Reputation: 23
This is My code:
This is iCloud with Coredata synchronization configuration code:
#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();
}
}
}
Here is the problem I encountered:
Crash mark
When iCloud data inside the change, I call the following method:
- (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]];
}];
}
This is the cause of the crash:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.
Upvotes: 0
Views: 329
Reputation: 1742
Try this.
_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]];
});
}
Upvotes: 1
Reputation: 1095
performBlock (and performBlockAndWait) can only be used for NSManagedObjectContexts that were initialised with either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType. By default NSManagedObjectContexts are initialised to use the NSConfinementConcurrencyType which does not support performBlock or performBlockAndWait.
You should change this line:
_managedObjectContext = [[NSManagedObjectContext alloc] init];
to either:
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
or
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Upvotes: 2