I have set up a nonhierarchical dual-MOC architecture (one for the main thread, one for a private thread) with save notifications for merging changes:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
NSManagedObjectContext* mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainContext setPersistentStoreCoordinator:coordinator];
[mainContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
_managedObjectContext = mainContext;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextDidSaveMainQueueContext:)
name:NSManagedObjectContextDidSaveNotification
object:_managedObjectContext];
}
return _managedObjectContext;
}
- (NSManagedObjectContext *)privateManagedObjectContext
{
if (_privateManagedObjectContext != nil) {
return _privateManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
NSManagedObjectContext* privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setPersistentStoreCoordinator:coordinator];
[privateContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
_privateManagedObjectContext = privateContext;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextDidSavePrivateQueueContext:)
name:NSManagedObjectContextDidSaveNotification
object:_privateManagedObjectContext];
}
return _privateManagedObjectContext;
}
- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
@synchronized(self) {
[self.managedObjectContext performBlock:^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
@synchronized(self) {
[self.privateManagedObjectContext performBlock:^{
[self.privateManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
}
Now, I've updated some object A on the main thread in the main MOC, and now I'm in some method and working within a block on my private MOC queue/thread:
[self.privateManagedObjectContext performBlockAndWait:^{
...
I save my main-thread MOC:
[self.managedObjectContext performBlockAndWait:^{
NSError *contextError;
if (![self.managedObjectContext save:&contextError]) NSLog(@"ERROR SAVING MOC : %@ ; %@", contextError, [contextError userInfo]);
}];
The save is successful (verified) and triggers the save notification (verified), which executes the merger.
But the MOCs remain inconsistent: when I then fetch object A from my main MOC and log the number of objects in its relationship R, I find that the number of objects differs from when I fetch object A from my private MOC and log the number of objects in relationship R.
A bit downstream of this, in a related chain of events, I go to save my private MOC. The app pauses (only if I have enabled an all-exceptions or all-objective-c-exceptions breakpoint), and execution can be resumed without any apparent harm. This issue is described here: core data MOC save pauses execution and fails to save (w/o error or crash). I have a feeling it's related, but I suspect this failure to properly merge is a more fundamental problem.
Other notes:
- I have tried every merge policy
- I found that if I attempt to execute the merger using performBlockAndWait, the app hangs indefinitely; I don't know whether that is the expected behavior.
- I've read every question I can find about this and tried everything I can think of.
Is there something wrong with this code? What else can I try? Thanks!
privateManagedObjectContextis called from more than one thread in parallel (same goes for you main MOC). Also, there is no need for your '@synchronized(self)'. make sure the main and private MOCs are not initialised twice - Dan Shelly@synchronized. I set a breakpoint and verified that the MOCs are initialized only once. If you think a race condition is likely coming into play here, I'd be happy for some elaboration, but it seems unlikely to me because I am testing this in a reasonably isolated fashion. No other methods/threads should be accessing core data during the execution of the method I'm troubleshooting, and the method always uses performBlockAndWait, as do all fetch requests. - mkc842persistentStoreCoordinatoris called from 2 different threads in parallel. this will cause each context to use a different coordinator or might even crash your application if the context does not strongly keep the coordinator. this might not be directly causing your merge issues, but its something you might want to take into account. - Dan ShellyNSPersistentStoreCoordinatorinstances will not cause a merge problem or the problems that the OP is describing. SQLite and Core Data are designed for multiple instances ofNSPersistentStoreCoordinatorto be running in parallel. - Marcus S. Zarra