我有一个应用程序使用单个UIManagedDocument作为数据存储,我使用Justin Driscoll's blog中的方法通过单例访问。
这很好用,但是当我为persistentStoreOptions设置我的普遍性密钥并更改状态通知时,应用程序只在沙箱中运行,并且没有网络活动。
这是执行流程...... 1.应用程序打开到创建单例的登陆屏幕。 2.一旦UIManagedDocument打开,我检查云权限,如果一切都好,我们有一个无处不在的容器,我用cloud contentNameKey和contentUrlKey选项覆盖persistentStoreOptions。
这是代码......
// DataStore.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
typedef void (^OnDocumentReady) (UIManagedDocument *appDataStore);
@interface DataStore : NSObject
@property (strong, nonatomic) UIManagedDocument *appDataStore;
+ (DataStore *)sharedDocumentHandler;
- (void)performWithDocument:(OnDocumentReady)onDocumentReady;
@end
// DataStore.m
#import "DataStore.h"
#import "HashDefines.h"
@interface DataStore ()
@property (nonatomic)BOOL preparingDocument;
@property (nonatomic, strong) NSFileCoordinator *coordinator;
@property (strong, nonatomic) NSString *localDocumentsPath;
@property (strong, nonatomic) NSURL *localDocumentsURL;
@property (strong, nonatomic) NSURL *localFileURL;
@end;
@implementation DataStore
@synthesize appDataStore = _appDataStore;
@synthesize localDocumentsPath = _localDocumentsPath;
@synthesize localDocumentsURL = _localDocumentsURL;
@synthesize localFileURL = _localFileURL;
static DataStore *_sharedInstance;
@synthesize coordinator = _coordinator;
#define LOCAL_DATA_STORE_FILE_NAME @“QR App DataStore"
#pragma mark - synthesiser overiders
+ (DataStore *)sharedDocumentHandler
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
#pragma mark - the juicy bits
#pragma mark - initialisers
- (id)init
{
self = [super init];
if (self) {
NSLog(@"appDataStore does NOT exist ... building one now");
[self initCoreDataInTheSandbox];
}
return self;
}
- (void)initCoreDataInTheSandbox
{
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
self.appDataStore = [[UIManagedDocument alloc] initWithFileURL:localURL];
// Set our document up for automatic migrations
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
self.appDataStore.persistentStoreOptions = options;
NSLog(@"1. DS persistentStoreOptions: %@", self.appDataStore.persistentStoreOptions);
}
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
NSLog(@"1. DS Begin performWithDocument: %@", self.appDataStore);
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
onDocumentReady(self.appDataStore);
NSLog(@"2. DS into the block ... onDocumentReady:");
self.preparingDocument = NO; // release in completion handler
};
if(!self.preparingDocument) {
NSLog(@"3. DS preparing document: dataStore.appDataStore: %@", self.appDataStore);
// "lock", so no one else enter here
self.preparingDocument = YES;
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.appDataStore.fileURL path]]) {
NSLog(@"4. DS creating document: dataStore.appDataStore: %@", self.appDataStore);
[self.appDataStore saveToURL:self.appDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateClosed) {
NSLog(@"5. DS dataStore.appDataStore.documentState: %d ... closed", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateSavingError) {
NSLog(@"6. DS dataStore.appDataStore.documentState: %d ... saving error", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateNormal) {
NSLog(@"7. DS dataStore.appDataStore.documentState: %d ... open", self.appDataStore.documentState);
OnDocumentDidLoad(YES);
}
} else {
// try until document is ready (opened or created by some other call)
NSLog(@"8. DS preparing document - NOT ... trying again");
[self performSelector:@selector(performWithDocument:) withObject:onDocumentReady afterDelay:0.5];
}
NSLog(@"9. DS Exiting performWithDocument: %@", self.appDataStore);
}
@end
// CloudConnector.h
#import "DataStore.h"
@interface CloudConnector : NSObject
- (void)hookUpCloudForDocument:(UIManagedDocument *)document;
(NSManagedObjectContext*)managedObjectContext;
- (NSString *) documentState: (int) state;
@end
#define ICLOUD_TOKEN_KEY @“com.apple.QR-App.UbiquityIdentityToken"
#define CLOUD_IS_UBIQUITOUS @"sweet ubiquity"
#define LOCAL_DATA_STORE_FILE_NAME @“QR App DataStore"
#define CLOUD_DATA_STORE_FILE_NAME @"com~app~QR-App~cloudstore"
//CloudConnector.m
#import "CloudConnector.h"
#import "HashDefines.h"
@interface CloudConnector ()
@property (strong, nonatomic) NSString *localDocumentsPath;
@property (strong, nonatomic) NSURL *localDocumentsURL;
@property (strong, nonatomic) NSURL *localFileURL;
@property (strong, nonatomic) NSURL *ubiquityDataFileURL;
@property (nonatomic)BOOL preparingDocument;
@property (nonatomic, strong) NSFileCoordinator *coordinator;
- (NSURL *) localFileURL: (NSString *) filename;
- (NSURL *) ubiquityDataFileURL: (NSString *) filename;
- (BOOL) isLocal: (NSString *) filename;
- (NSString *) documentState: (int) state;
@end;
@implementation CloudConnector
@synthesize coordinator = _coordinator;
@synthesize localDocumentsPath = _localDocumentsPath;
@synthesize localDocumentsURL = _localDocumentsURL;
@synthesize localFileURL = _localFileURL;
@synthesize ubiquityDataFileURL = _ubiquityDataFileURL;
#pragma mark - synthesiser overiders
#pragma mark - the juicy bits
- (void)checkUbiquityStatus
{
id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (currentiCloudToken) {
NSData *newTokenData =
[NSKeyedArchiver archivedDataWithRootObject: currentiCloudToken];
[[NSUserDefaults standardUserDefaults]
setObject: newTokenData
forKey: ICLOUD_TOKEN_KEY];
} else {
[[NSUserDefaults standardUserDefaults]
removeObjectForKey: ICLOUD_TOKEN_KEY];
}
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename forContainer: (NSString *) container
{
if (!filename) return nil;
NSURL *fileURL = [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename
{
return [self ubiquityDataFileURL:filename forContainer:nil];
}
#pragma mark Ubiquity Data
- (NSURL *) ubiquityDataURLForContainer: (NSString *) container
{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container];
}
- (NSArray *) contentsOfUbiquityDataFolderForContainer: (NSString *) container
{
NSURL *targetURL = [self ubiquityDataURLForContainer:container];
if (!targetURL) return nil;
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:targetURL.path error:nil];
return array;
}
- (BOOL) isLocal: (NSString *) filename
{
if (!filename) return NO;
NSURL *targetURL = [self localFileURL:filename];
if (!targetURL) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path];
}
- (NSString *) documentState: (int) state
{
if (!state) return @"Document state is normal";
NSMutableString *string = [NSMutableString string];
if ((state & UIDocumentStateClosed) != 0)
[string appendString:@"Document is closed\n"];
if ((state & UIDocumentStateInConflict) != 0)
[string appendString:@"Document is in conflict"];
if ((state & UIDocumentStateSavingError) != 0)
[string appendString:@"Document is experiencing saving error"];
if ((state & UIDocumentStateEditingDisabled) != 0)
[string appendString:@"Document editing is disbled" ];
return string;
}
#pragma mark - initialisers
- (void)hookUpCloudForDocument:(UIManagedDocument *)document
{
// checking for ubiquity
NSLog(@"checking for ubiquity");
[self checkUbiquityStatus];
// THE FOLLOWING CODE DOESN'T WORK
if ([[NSUserDefaults standardUserDefaults] objectForKey:ICLOUD_TOKEN_KEY]) {
// cloud permissions are good to go.
NSLog(@"cloud permissions are good to go.");
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSURL *url = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier: nil];
NSLog(@"bikky url: %@", url);
if (url) { // != nil) {
// Your app can write to the ubiquity container
dispatch_async (dispatch_get_main_queue (), ^(void) {
// On the main thread, update UI and state as appropriate
[self setupCoreDataInTheCloudForDocument:document];
});
}
});
}
}
- (void) setupCoreDataInTheCloudForDocument:(UIManagedDocument *)document
{
NSLog(@"1. cc.setupCoreDataInTheCloudForDocument: %@", document);
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
NSURL *cloudURL = [self ubiquityDataFileURL:CLOUD_DATA_STORE_FILE_NAME];
// Set the persistent store options to point to the cloud
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
// [document.fileURL lastPathComponent], NSPersistentStoreUbiquitousContentNameKey,
localURL, NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
document.persistentStoreOptions = options;
NSLog(@"2. cc.document.persistentStoreOptions: %@", document.persistentStoreOptions);
// Register as presenter
self.coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:document];
[NSFileCoordinator addFilePresenter:document];
// // THIS CODE IS INLINE FROM iOS 5 COOK BOOK
// // I EXECUTE THIS IN (void)performWithDocument:(OnDocumentReady)onDocumentReady
//
// // Check at the local sandbox
// if ([self isLocal:LOCAL_DATA_STORE_FILE_NAME])
// {
// NSLog(@"Attempting to open existing file");
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(@"Error opening file"); return;}
// NSLog(@"File opened");
// }];
// }
// else
// {
// NSLog(@"Creating file.");
// // 1. save it out, 2. close it, 3. read it back in.
// [document saveToURL:localURL
// forSaveOperation:UIDocumentSaveForCreating
// completionHandler:^(BOOL success){
// if (!success) { NSLog(@"7. Error creating file"); return; }
// NSLog(@"File created");
// [document closeWithCompletionHandler:^(BOOL success){
// NSLog(@"Closed new file: %@", success ? @"Success" : @"Failure");
//
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(@"Error opening file for reading."); return;}
// NSLog(@"File opened for reading.");
// }];
// }];
// }];
// }
//}
@end
我按照以下方式调用单身人士......
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.codeGeneratorDataStore) {
NSLog(@"MVC.viewDidLoad: Grab local instance of document from data store singleton");
[[DataStore sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.codeGeneratorDataStore = document;
// Do stuff with the document, set up a fetched results controller, whatever.
// NSLog(@"IN: _codeGeneratorDataStore.documentState : %u", _codeGeneratorDataStore.documentState);
if (![self.codeGeneratorDataStore.persistentStoreOptions objectForKey:NSPersistentStoreUbiquitousContentNameKey]) {
NSLog(@"MVC.viewDidLoad: We have a document, hooking up cloud now ... \n%@", document);
[self.cloudConnector hookUpCloudForDocument:self.codeGeneratorDataStore];
}
[self setupFetchedResultsController];
}];
NSLog(@"GVC.viewDidLoad: Subscribing to notifications");
[self subscribeToCloudNotifications];
}
[self checkDocumentStatus];
}
然后我检查文件状态......
- (void)checkDocumentStatus {
NSLog(@"MVC.checkDocumentStatus\n Document: %@\n persistentStoreOptions: %@", self.codeGeneratorDataStore, self.codeGeneratorDataStore.persistentStoreOptions);
if (![[
NSFileManager defaultManager] fileExistsAtPath:[self.codeGeneratorDataStore.fileURL path]]) {
[self.codeGeneratorDataStore saveToURL:self.codeGeneratorDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(@"1. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateClosed) {
[self.codeGeneratorDataStore openWithCompletionHandler:^(BOOL succes) {
NSLog(@"2. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateNormal) {
NSLog(@"3. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);
}
}
设置并注册通知......
- (void)subscribeToCloudNotifications
{
// subscribe to the NSPersistentStoreDidImportUbiquitousContentChangesNotification notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentStateChanged:)
name:UIDocumentStateChangedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentContentsDidUpdate:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:nil];
NSLog(@"MVC.subscribeToCloudNotifications: notification subscriptions are good to go");
}
- (void) documentContentsDidUpdate: (NSNotification *) notification
{
NSLog(@"CMVC notification: documentContentsDidUpdate");
NSDictionary *userInfo = notification.userInfo;
[self.codeGeneratorDataStore.managedObjectContext performBlock:^{[self mergeiCloudChanges:userInfo forContext:self.codeGeneratorDataStore.managedObjectContext];}];
}
// When notified about a cloud update, start merging changes
- (void)documentStateChanged: (NSNotification *)notification
{
NSLog(@"CMVC notification: documentStateChanged");
// NSLog(@"Document state change: %@", [CloudHelper documentState:self.codeGeneratorDataStore.documentState]);
UIDocumentState documentState = self.codeGeneratorDataStore.documentState;
if (documentState & UIDocumentStateInConflict)
{
// This application uses a basic newest version wins conflict resolution strategy
NSURL *documentURL = self.codeGeneratorDataStore.fileURL;
NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:documentURL];
for (NSFileVersion *fileVersion in conflictVersions) {
fileVersion.resolved = YES;
}
[NSFileVersion removeOtherVersionsOfItemAtURL:documentURL error:nil];
}
}
// Merge the iCloud changes into the managed context
- (void)mergeiCloudChanges:(NSDictionary*)userInfo forContext:(NSManagedObjectContext*)managedObjectContext
{
@autoreleasepool
{
// NSLog(@"Merging changes from cloud");
NSMutableDictionary *localUserInfo = [NSMutableDictionary dictionary];
NSSet *allInvalidations = [userInfo objectForKey:NSInvalidatedAllObjectsKey];
NSString *materializeKeys[] = { NSDeletedObjectsKey, NSInsertedObjectsKey };
if (nil == allInvalidations)
{
// (1) we always materialize deletions to ensure delete propagation happens correctly, especially with
// more complex scenarios like merge conflicts and undo. Without this, future echoes may
// erroreously resurrect objects and cause dangling foreign keys
// (2) we always materialize insertions to make new entries visible to the UI
int c = (sizeof(materializeKeys) / sizeof(NSString *));
for (int i = 0; i < c; i++)
{
NSSet *set = [userInfo objectForKey:materializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
[objectSet addObject:[managedObjectContext objectWithID:moid]];
[localUserInfo setObject:objectSet forKey:materializeKeys[i]];
}
}
// (3) we do not materialize updates to objects we are not currently using
// (4) we do not materialize refreshes to objects we are not currently using
// (5) we do not materialize invalidations to objects we are not currently using
NSString *noMaterializeKeys[] = { NSUpdatedObjectsKey, NSRefreshedObjectsKey, NSInvalidatedObjectsKey };
c = (sizeof(noMaterializeKeys) / sizeof(NSString*));
for (int i = 0; i < 2; i++)
{
NSSet *set = [userInfo objectForKey:noMaterializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
{
NSManagedObject *realObj = [managedObjectContext objectRegisteredForID:moid];
if (realObj)
[objectSet addObject:realObj];
}
[localUserInfo setObject:objectSet forKey:noMaterializeKeys[i]];
}
}
NSNotification *fakeSave = [NSNotification notificationWithName:NSManagedObjectContextDidSaveNotification object:self userInfo:localUserInfo];
[managedObjectContext mergeChangesFromContextDidSaveNotification:fakeSave];
}
else
[localUserInfo setObject:allInvalidations forKey:NSInvalidatedAllObjectsKey];
[managedObjectContext processPendingChanges];
// [self performSelectorOnMainThread:@selector(performFetch) withObject:nil waitUntilDone:NO];
}
}
我们很高兴 - 应用程序运行但没有网络活动。为什么不上传交易日志?
这里非常感谢任何帮助。它现在已经开了一个星期了,我现在已经结束了。
答案 0 :(得分:0)
我之前在电子邮件中回答了这个问题,但我也在这里提出我的答案,以便完整。
这是仅限iOS 7的应用,还是需要支持iOS 5或6?
我不建议将iCloud和Core Data混合在任何仍然需要支持iOS 5和6的内容中。但是,对iOS 7的新支持看起来非常可靠。我没有用它部署一个数百万的用户应用程序 - 所以我不会说它经过了战斗测试,但它似乎更加强大。在我的初步测试中,它处理了我扔的所有内容,没有任何问题。
此外,您是否关注iOS 7的最新示例代码?看起来它正在遵循一些较旧的模式。这可能会导致很多问题,因为iOS 7中系统运行的方式发生了很大变化。
例如,在iOS 7中,我们通常只需要设置NSPersistentStoreUbiquitousContentNameKey
。除非我们尝试支持旧版核心数据堆栈,否则我们无需设置内容URL密钥。
此外,iOS 7下核心数据堆栈的设置存在巨大差异。当我们首次设置iCloud持久存储时,iOS 7将自动为我们创建本地副本(后备存储)。我们可以使用商店 - 但更改只会存储在本地。当iCloud版本准备就绪后,它将发送NSPersistentStoreCoordinatorStoresWillChangeNotification
。我们应该保存所有更改,然后重置我们的托管对象上下文。
同样,您的代码似乎是手动设置自己的后备存储 - 这可能是问题的一部分。
如果您正在使用旧示例,我实际上建议您抛弃该代码并重新开始。观看WWDC 2013上的“Core Data和iCloud中的新功能”视频。它对新技术和当前的最佳实践做了很好的了解。
我的猜测是你正在获得后备商店,但它永远不会切换到iCloud商店。运行应用程序时查看控制台日志。你应该看到如下内容:
-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](754): CoreData: p Ubiquity:
mobile~C9C8554A-BD44-43C3-AC54-603046EF0162:com~freelancemad p science~HealthBeat
Using local storage: 1
此处,“使用本地存储:1”表示您正在使用后备存储。在一两分钟(或有时更长)中,您将看到类似的消息“使用本地存储:0”。这意味着您现在正在使用iCloud商店。
此外,运行应用程序时,请务必在Xcode中打开Debug Navigator。它应该有一个iCloud的分析器,它会在数据上传到云或从云下载时显示。
我希望有所帮助,
富 -