iCloud 与 Coredata 同步

iCloud with Coredata synchronization

这是我的代码:
这是iCloud with Coredata同步配置代码:

#pragma mark - Core Data stack

        @synthesize managedObjectContext = _managedObjectContext;
        @synthesize managedObjectModel = _managedObjectModel;
        @synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

        - (NSURL *)applicationDocumentsDirectory {
            // The directory the application uses to store the Core Data store file. This code uses a directory named "com.wanglichen.iPassword" in the application's documents directory.
            return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
        }

        - (NSManagedObjectModel *)managedObjectModel {
            // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
            if (_managedObjectModel != nil) {
                return _managedObjectModel;
            }
            NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"iPassword" withExtension:@"momd"];
            _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
            return _managedObjectModel;
        }

        - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
            // create a new persistent store of the appropriate type
            NSError *error = nil;
            NSURL *storeURL = [self applicationDocumentsDirectory];

            _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

            // ** Note: if you adapt this code for your own use, you MUST change this variable.
            // is the full App ID (including the Team Prefix). You will need to change this to match the Team Prefix found in your own iOS Provisioning Portal.
            NSString *iCloudEnabledAppID = [[NSBundle mainBundle] infoDictionary][@"CFBundleIdentifier"];

            // the name of the SQLite database store file.
            NSString *dataFileName = @"iPassword.sqlite";

            // ** Note: For basic usage you shouldn't need to change anything else
            // dataDirectory is the name of the directory the database will be stored in. It should always end with .nosync
            // iCloudData = iCloudRootPath + dataDirectory

            NSString *iCloudDataDirectoryName = @"Data.nosync";
            // logsDirectory is the name of the directory the database change logs will be stored in.
            NSString *iCloudLogsDirectoryName = @"Logs";
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSURL *localStore = [storeURL URLByAppendingPathComponent:dataFileName];

            // iCloudRootPath is the URL to your apps iCloud root path.
            NSURL *iCloudRootPath = [fileManager URLForUbiquityContainerIdentifier:nil];

            if (iCloudRootPath) // If iCloud is working, save it to iCloud container.
            {
                NSLog(@"iCloud is working.");

                // Place core data sqlite file in iCloudRootPath/Data.nosync/         (The subdirectory should be ended with .nosync)
                // Place the log file in iCloudRootPath/Logs        (All changed in iCloud will be download to log file firstly.)

                NSURL *iCloudLogsPath = [iCloudRootPath URLByAppendingPathComponent:iCloudLogsDirectoryName];
        //        NSLog(@"iCloudEnabledAppID = %@",iCloudEnabledAppID);
        //        NSLog(@"dataFileName = %@", dataFileName);
        //        NSLog(@"iCloudDataDirectoryName = %@", iCloudDataDirectoryName);
        //        NSLog(@"iCloudLogsDirectoryName = %@", iCloudLogsDirectoryName);
        //        NSLog(@"iCloud = %@", iCloudRootPath);
        //        NSLog(@"iCloudLogsPath = %@", iCloudLogsPath);

                NSURL *iCloudDataURL = [iCloudRootPath URLByAppendingPathComponent:iCloudDataDirectoryName];
                if ([fileManager fileExistsAtPath:[iCloudDataURL path]] == NO)
                {
                    NSError *fileSystemError;
                    [fileManager createDirectoryAtPath:[iCloudDataURL path]
                           withIntermediateDirectories:YES
                                            attributes:nil
                                                 error:&fileSystemError];
                    if(fileSystemError != nil)
                    {
                        NSLog(@"Error creating database directory %@", fileSystemError);
                    }
                }
                iCloudDataURL = [iCloudDataURL URLByAppendingPathComponent:dataFileName];
        //        NSLog(@"iCloudDataPath = %@", iCloudDataURL);

                NSMutableDictionary *options = [NSMutableDictionary dictionary];
                [options setObject:@(YES)                       forKey:NSMigratePersistentStoresAutomaticallyOption];
                [options setObject:@(YES)                       forKey:NSInferMappingModelAutomaticallyOption];
                [options setObject:iCloudEnabledAppID           forKey:NSPersistentStoreUbiquitousContentNameKey];
                [options setObject:iCloudLogsPath               forKey:NSPersistentStoreUbiquitousContentURLKey];

                [_persistentStoreCoordinator lock];

                if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                               configuration:nil
                                                                         URL:iCloudDataURL
                                                                     options:options
                                                                       error:nil])
                {
                    NSDictionary *ui = [error userInfo];
                    for(NSString *err in [ui keyEnumerator]) {
                        NSLog(@"err:%@",[ui objectForKey:err]);
                    }
                    abort();
                }

                [_persistentStoreCoordinator unlock];
            }
            else    // If iCloud is not working, save it to local.
            {
                NSLog(@"iCloud is NOT working - using a local store");
                NSMutableDictionary *options = [NSMutableDictionary dictionary];
                [options setObject:@(YES)   forKey:NSMigratePersistentStoresAutomaticallyOption];
                [options setObject:@(YES)   forKey:NSInferMappingModelAutomaticallyOption];
                [options setObject:@(YES)   forKey:NSMigratePersistentStoresAutomaticallyOption];
                [options setObject:@(YES)   forKey:NSInferMappingModelAutomaticallyOption];
                [_persistentStoreCoordinator lock];

                if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                               configuration:nil
                                                                         URL:localStore
                                                                     options:options
                                                                       error:nil])
                {
                    NSDictionary *ui = [error userInfo];
                    for(NSString *err in [ui keyEnumerator]) {
                        NSLog(@"err:%@",[ui objectForKey:err]);
                    }
                    abort();
                }
                [_persistentStoreCoordinator unlock];
            }

            return _persistentStoreCoordinator;
        }


        - (NSManagedObjectContext *)managedObjectContext {
            // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
            if (_managedObjectContext != nil) {
                return _managedObjectContext;
            }

            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
            if (!coordinator) {
                return nil;
            }
            _managedObjectContext = [[NSManagedObjectContext alloc] init];
            [_managedObjectContext setPersistentStoreCoordinator:coordinator];

            // Register NSPersistentStoreDidImportUbiquitousContentChangesNotification, so that
            // coreDataChangedIniCloud will be called if core data in iCloud is changed.
            [[NSNotificationCenter defaultCenter]addObserver:self
                                                    selector:@selector(coreDataChangedIniCloud:)
                                                        name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
                                                      object:self.persistentStoreCoordinator];
            return _managedObjectContext;
        }

        - (void)coreDataChangedIniCloud:(NSNotification *)notification
        {
            NSLog(@"Merging in changes from iCloud...");

            [self.managedObjectContext performBlock:^{

                [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
                NSLog(@"new data from iCloud: %@", notification.object);
                [[NSNotificationCenter defaultCenter] postNotificationName:@"MergingInChangesFromICloud" object:notification.object userInfo:[notification userInfo]];
            }];
        }

        #pragma mark - Core Data Saving support

        - (void)saveContext {
            NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
            if (managedObjectContext != nil) {
                NSError *error = nil;
                if ([managedObjectContext hasChanges] && ![managedObjectContext save:&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.
                    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                    abort();
                }
            }
        }

下面是我遇到的问题:

崩溃标记

当iCloud里面的数据发生变化时,我调用了下面的方法:

- (void)coreDataChangedIniCloud:(NSNotification *)notification
{
    NSLog(@"Merging in changes from iCloud...");

    [self.managedObjectContext performBlock:^{

        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        NSLog(@"new data from iCloud: %@", notification.object);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MergingInChangesFromICloud" object:notification.object userInfo:[notification userInfo]];
    }];
}

这是崩溃的原因:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.

试试这个。

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];


- (void)coreDataChangedIniCloud:(NSNotification *)notification
    NSManagedObjectContext *moc = self.managedObjectContext;
            [moc performBlockAndWait:^{
                [moc mergeChangesFromContextDidSaveNotification:notification];
            }];
    dispatch_async(dispatch_get_main_queue(), ^{
       [[NSNotificationCenter defaultCenter] postNotificationName:@"MergingInChangesFromICloud" object:notification.object userInfo:[notification userInfo]];
    });
    }

performBlock(和 performBlockAndWait)只能用于使用 NSPrivateQueueConcurrencyType 或 NSMainQueueConcurrencyType 初始化的 NSManagedObjectContext。默认情况下,NSManagedObjectContexts 被初始化为使用不支持 performBlock 或 performBlockAndWait 的 NSConfinementConcurrencyType。

您应该更改此行:

_managedObjectContext = [[NSManagedObjectContext alloc] init];

至:

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];