I use a nested context template to support multi-threaded work with CoreData. I have a singleton class CoredDataManager, and contexts:
self.masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; self.masterContext.persistentStoreCoordinator = self.persistentStoreCoordinator; self.mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; self.mainContext.parentContext = self.masterContext;
For each insert operation in response from a web service, I use the API of my CoreDataManager to get a new managed context:
- (NSManagedObjectContext *)newManagedObjectContext { NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; workerContext.parentContext = self.mainContext; return workerContext; }
It looks something like this (the PlayerView class is a subclass of the NSManagedObject class):
[PlayerView insertIfNeededByUniqueKey:@"playerViewId" value:playerViewId inBackgroundWithCompletionBlock:^(NSManagedObjectContext *context, PlayerView *playerView) { playerView.playerViewId = playerViewId; playerView.username = playerViewDictionary[@"name"]; [context saveContextWithCompletionBlock:^{ //do something } onMainThread:NO];//block invocation on background thread }];
The saveContextWithCompletionBlock method is implemented in the NSManagedObjectContext category:
- (void)saveContextWithCompletionBlock:(SaveContextBlock)completionBlock onMainThread:(BOOL)onMainThread { __block NSError *error = nil; if (self.hasChanges) { [self performBlock:^{ [self save:&error]; if (error) { @throw [NSException exceptionWithName:NSUndefinedKeyException reason:[NSString stringWithFormat:@"Context saving error: %@\n%@\n%@", error.domain, error.description, error.userInfo] userInfo:error.userInfo]; } if (completionBlock) { if (onMainThread && [NSThread isMainThread]) { completionBlock(); } else if (onMainThread) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } else if ([NSThread isMainThread]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{ completionBlock(); }); } else { completionBlock(); } } }]; } }
Then at some point I call the CoreDataManager method to save the main context:
- (void)saveMasterContext; { __block NSError *error; [self.mainContext performBlock:^{ [self.mainContext save:&error]; [self treatError:error]; [self.masterContext performBlock:^{ [self.masterContext save:&error]; [self treatError:error]; }]; }]; }
I have two main classes: subclasses of NSManagedObject - PlayerView and Post. PlayerView is related to one to many to Post. PlayerView is saved and everything is in order. The message is never saved, and I get an error message:
CoreData: error: Mutation of the managed object 0x17dadd80 (0x17daf930) after removing it from the context.
I think the problem in context preserves logic.