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 同步