I am using a Private Managed Object Context to create some new objects into the persistent store, then after saving the private MOC, merging them into the main MOC using mergeChangesFromContextDidSaveNotification. This works fine, and updates the UI as required, and the NSManagedObjectContextWillSaveNotification is NOT invoked here for the mainMOC.
Then I make some changes to the mainMOC using the UI, and listen to NSManagedObjectContextWillSaveNotification. The notification is posted, but it contains not only the edits I made, but also the objects that were merged in from the PrivateMOC using mergeChangesFromContextDidSaveNotification.
Is there a way to ignore the changes that were merged in from another context into the mainContext, on subsequent contextDidChange notifications?
Here is the setup:
- (void) loadData { privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; privateContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextWillSave:) name:NSManagedObjectContextWillSaveNotification object: self.mainContext]; NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:record.recordType inManagedObjectContext: self.privateContext]; // fill in object if ([self.privateContext hasChanges]) { [self savePrivateContextAndMergeWithMainContext: self.privateContext]; } } - (void) savePrivateContextAndMergeWithMainContext: (NSManagedObjectContext *) privateContext { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(privateContextDidChange:) name:NSManagedObjectContextDidSaveNotification object:privateContext]; __block NSError *error = nil; [privateContext performBlockAndWait:^{ NSLog(@"PrivateContext saved"); [privateContext save:&error]; }]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:privateContext]; if (error) { NSLog(@"error = %@", error); } } - (void) privateContextDidChange: (NSNotification *) notification{ [self.mainContext performBlockAndWait:^{ NSLog(@"merged into mainContext"); [self.mainContext mergeChangesFromContextDidSaveNotification:notification]; }]; } This works fine and saving the private context and merging into the mainContext doesn't trigger a contextWillSave notification. But on editing the data from the UI (on the main MOC) triggers the notification and includes the data that was previously saved using the private MOC.
Hope that's clear. Let me know if I should include anything else.
-- UPDATE --
Seems like the problem is with specifically deleting objects from the private context. After deleting from the private context, and calling mergeChangesFromContextDidSaveNotification on the main MOC, the mainMoc's deletedObjects set still shows the object that was deleted. This doesn't happen with inserts or updates in the private context. Is this documented anywhere? What could be the workaround?
2 Answers
Answers 1
Core Data has been around for a number of years now, and over that time the concurrency model has evolved.
The "thread confinement" and "locking" concurrency models used notifications to communicate changes between contexts. When a context was saved a notification would be broadcast with information about the changes. That information could be used to merge the changes from one context into others. This never scaled particularly well and was often a major pain point for applications. The "locking" and "thread confinement" models have been obsolete for a number of years now.
When "queue confinement" was introduced the concept of "nested contexts" was introduced along with it. A context could have a parent context, and when the child context is saved those changes are automatically communicated to the parent (and no further). This is the recommended way to communicate changes between contexts when using queue confinement, which you are.
It is not recommended that you use mergeChangesFromContextDidSaveNotification: with a NSPrivateQueueConcurrencyType context.
Core Data performs a lot of internal bookkeeping and change tracking on your behalf. Normally during a save operation that information is aggregated and coalesced - this is part of the processPendingChanges operation. When the save operation is performed inside a performBlockAndWait: this may not happen, or may not be complete - which results in the changes (and any change notification) being incomplete or not happening at all. performBlockAndWait: is reentrant, which is not compatible with the processPendingChanges process. Using performBlock: instead will result in more consistent behavior.
If you use nested contexts to communicate changes rather than notifications your issue as you describe in your question will be solved.
Answers 2
Modifying privateContextDidChange like this ...
- (void) privateContextDidChange: (NSNotification *) notification{ if (notification.object == PrivateMOC) { [self.mainContext performBlockAndWait:^{ NSLog(@"merged into mainContext"); [self.mainContext mergeChangesFromContextDidSaveNotification:notification]; }]; } } ... where PrivateMOC is the reference to the Private Managed Object Context?
0 comments:
Post a Comment