iCloud 与 Coredata 同步

Posted

技术标签:

【中文标题】iCloud 与 Coredata 同步【英文标题】:iCloud with Coredata synchronization 【发布时间】:2015-01-12 09:14:52 【问题描述】:

这是我的代码: 这是iCloud与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.

【问题讨论】:

你会发布异常(在代码标签中)吗?谢谢。 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“只能在使用队列创建的 NSManagedObjectContext 上使用 -performBlock:。 错误标签 - (void)coreDataChangedIniCloud:(NSNotification *)notification 【参考方案1】:

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

你应该改变这一行:

_managedObjectContext = [[NSManagedObjectContext alloc] init];

到任一:

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

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

【讨论】:

非常感谢!错误已修复。【参考方案2】:

试试这个。

_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]];
    );
    

【讨论】:

也报了同样的错误***由于未捕获的异常'NSInvalidArgumentException'而终止应用程序,原因:'只能在使用队列创建的NSManagedObjectContext上使用-performBlockAndWait。' 试试这个示例代码。 iCloudStoreManager 感谢您的帮助!

以上是关于iCloud 与 Coredata 同步的主要内容,如果未能解决你的问题,请参考以下文章

Core Data 与 Ensembles 的 iCloud 同步

使用 iCloud 在多台设备上同步 Core Data

在应用程序运行时启用/禁用 Core Data 的 iCloud 同步

iCloud 与 Coredata 同步

CoreData 与 iCloud,不与同步通知一起使用

全局标识符? - iCloud + Core Data + Ensembles - 删除对象时重复