Mac / iPhone应用程序-使用核心数据将数据同步到iCloud和设备(使用核心数据)

6

我正在开发一款iPhone应用和一款 Mac 应用,它们都使用 Core Data。

我希望这两个应用通过 iCloud 存储同步它们的数据库。

我已经对 managedObjectContextpersistentStoreCoordinator 的实现进行了调整,并添加了 mergeiCloudChanges 方法——参考了更新后的 Recipes 示例代码:

#pragma mark -
#pragma mark Core Data stack

// this takes the NSPersistentStoreDidImportUbiquitousContentChangesNotification
// and transforms the userInfo dictionary into something that
// -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] can consume
// then it posts a custom notification to let detail views know they might want to refresh.
// The main list view doesn't need that custom notification because the NSFetchedResultsController is
// already listening directly to the NSManagedObjectContext
- (void)mergeiCloudChanges:(NSNotification*)note forContext:(NSManagedObjectContext*)moc {

    NSLog(@"merging iCloud stuff");

    [moc mergeChangesFromContextDidSaveNotification:note]; 

    NSNotification* refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self  userInfo:[note userInfo]];

    [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
}

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext
{
    if (managedObjectContext != nil)
    {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil)
    {
        if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0")) {
            NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

            [moc performBlockAndWait:^{
                [moc setPersistentStoreCoordinator: coordinator];

                [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
            }];
            managedObjectContext = moc;
        } else {
            managedObjectContext = [[NSManagedObjectContext alloc] init];
            [managedObjectContext setPersistentStoreCoordinator:coordinator];
        }

    }
    return managedObjectContext;
}

// NSNotifications are posted synchronously on the caller's thread
// make sure to vector this back to the thread we want, in this case
// the main thread for our views & controller
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {


     NSManagedObjectContext* moc = [self managedObjectContext];

    // this only works if you used NSMainQueueConcurrencyType
    // otherwise use a dispatch_async back to the main thread yourself
    [moc performBlock:^{
        [self mergeiCloudChanges:notification forContext:moc];
    }];
}


/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
 */
- (NSManagedObjectModel *)managedObjectModel {

    if (managedObjectModel != nil) {
        return managedObjectModel;
    }
    managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];    
    return managedObjectModel;
}





- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator__ != nil) {
        return persistentStoreCoordinator__;
    }

    // assign the PSC to our app delegate ivar before adding the persistent store in the background
    // this leverages a behavior in Core Data where you can create NSManagedObjectContext and fetch requests
    // even if the PSC has no stores.  Fetch requests return empty arrays until the persistent store is added
    // so it's possible to bring up the UI and then fill in the results later
    persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];


    // prep the store path and bundle stuff here since NSBundle isn't totally thread safe
    NSPersistentStoreCoordinator* psc = persistentStoreCoordinator__;
     NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"MyApp.sqlite"];

    // do this asynchronously since if this is the first time this particular device is syncing with preexisting
    // iCloud content it may take a long long time to download
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSFileManager *fileManager = [NSFileManager defaultManager];

        NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
        // this needs to match the entitlements and provisioning profile
        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
        NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"MyApp"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

        NSLog(@"cloudURL: %@", cloudURL);        

        //  The API to turn on Core Data iCloud support here.
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"xxxxxxxx.com.me.MyApp", 
                                 @"MyApp", 
                                 cloudURL, 
                                 NSPersistentStoreUbiquitousContentURLKey, 
                                 [NSNumber numberWithBool:YES], 
                                 NSMigratePersistentStoresAutomaticallyOption, 
                                 [NSNumber numberWithBool:YES], 
                                 NSInferMappingModelAutomaticallyOption,
                                 nil];

        NSError *error = nil;

        [psc lock];
        if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.

             Typical reasons for an error here include:
             * The persistent store is not accessible
             * The schema for the persistent store is incompatible with current managed object model
             Check the error message to determine what the actual problem was.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }    
        [psc unlock];

        // tell the UI on the main thread we finally added the store and then
        // post a custom notification to make your views do whatever they need to such as tell their
        // NSFetchedResultsController to -performFetch again now there is a real store
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"asynchronously added persistent store!");
            [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil];
        });
    });

    return persistentStoreCoordinator__;
}

当我构建/运行我的应用程序时,我可以看到文件出现在“/Users/me/Library/Mobile Documents”目录中。
但是我不知道它是否正在同步到iCloud存储 - 显然iPhone和Mac之间的数据没有同步。
我需要实现其他方法来使数据转移到云端吗?
并且有没有办法查看实际位于iCloud存储中的文档?

3个回答

3
这里是一个快速的部分答案。
您可以查看iCloud中存储的内容:
在Mac上:
系统偏好设置.app -> iCloud -> 点击“管理...”然后您将看到所有应用程序的列表,这些应用程序在Mac OS X或iOS上存储了文档。
在iOS上:
首选项 -> iCloud -> 存档和备份 -> 在“已使用空间”下面的选项中,您将看到所有在Mac OS X或iOS上存储文档的应用程序的列表。
只要您使用NSFileManager的setUbiquitous:itemAtURL:destinationURL:error:方法,文档就应该被发送到iCloud,并在其他设备上显示。

嗨,Mike,感谢您的回复。 我已经通过系统偏好设置检查了我的iCloud存储。看起来没有任何东西在复制。我们继续寻找。 还有其他提示吗? 另外,是的,我正在使用NSFileManager的setUbiquitous。 - adamteale
啊,实际上我没有使用“setUbiquitous: itemAtURL: destinationURL: error:” - 我正在使用“addPersistentStoreWithType”,并传递一个包含NSPersistentStoreUbiquitousContentNameKey的选项字典。这是相同的想法吗? 我会阅读...再次感谢。 - adamteale
我注意到我的应用程序在这一行卡住了:if ([[NSFileManager defaultManager] setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {所以我停止了Xcode中的应用程序,然后在控制台应用程序中出现了这个错误:librariand: client connection is invalid: Connection invalid有任何想法吗? - adamteale
连接无效的消息只是意味着应用程序已经关闭,因此没有更多的连接。如果您正在使用基于文档的应用程序,则需要使用 setUbiquitous:...。当我遇到问题时,我在应用程序的一个初始化方法中添加了 NSLog(@“container ==%@”,[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container]);,以查看是否甚至获取了URL。您还应确保在将文档移动到其他地方之前,它已被写入磁盘。 - theMikeSwan
嗨马克,谢谢你的帮助,我现在改变了我的应用程序工作方式,最终我可以看到一些同步发生在我的 iPhone 和 Mac 上。尽管我不能说这完全流畅。我将要问一个关于我的新问题的新问题!再次感谢您的帮助! - adamteale

1

0

好的,我的代码看起来有点不同,我将它放在一个单独的类中以便在所有项目中重用。但是,如果启用了iCloud(URLForUbiquityContainerIdentifier:nil返回非nil),我会像这样设置我的NSPersistentStoreCoordinator:

// ---- iCloud Setup 

// fist container in entitlements
NSURL *iCloudDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; 

// if iCloud is enabled setup the store
if (iCloudDirectoryURL) {

    __iCloudEnabled = true;

    NSLog(@"iCloud:%@", [iCloudDirectoryURL absoluteString]);
    // AppDelegate has to provide the contentnamekey
    NSString *contentNameKey = [appDelegate dataStoreContentNameKey];

     options = [NSDictionary dictionaryWithObjectsAndKeys:contentNameKey, NSPersistentStoreUbiquitousContentNameKey, iCloudDirectoryURL, NSPersistentStoreUbiquitousContentURLKey, nil];
}

我错过了您设置NSPersistentStoreUbiquitousContentNameKey的位置。 第二个明显,我猜这只在设备上起作用,您的App ID需要启用iCloud。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接