8

I have an application using Core Data with the following, fairly standard, managed object context hierarchy:

Persistent Store Coordinator ↳ Save Context (Private Queue Concurrency Type) ↳ Main Context (Main Queue Concurrency Type) ↳ Private Context (Private Queue Concurrency Type) 

The merge policy for all managed object contexts is set to NSMergeByPropertyObjectTrumpMergePolicy

I am observing NSManagedObjectContextDidSaveNotification which will invoke the following function when the Private Context is saved and merge changes to the Main Context:

func contextDidSaveNotificationHandler(notification: NSNotification) { if let savedContext = notification.object as? NSManagedObjectContext { if savedContext == privateObjectContext { mainObjectContext.performBlock({ if let updatedObjects = notification.userInfo![NSUpdatedObjectsKey] as? Set<NSManagedObject> { // // fire faults on the updated objects // for obj in updatedObjects { mainObjectContext.objectWithID(obj.objectID).willAccessValueForKey(nil) } } mainObjectContext.mergeChangesFromContextDidSaveNotification(notification) }) } } } 

This is working most of the time but sometimes I am finding that changes to existing objects in the Private Context are not being merged into the Main Context. I can't figure out why -- the private context save is successful; the NSManagedObjectContextDidSaveNotification notification is being sent; the notification handler is being invoked; notification.userInfo?[NSUpdatedObjectsKey] contains the correctly updated objects; but at the end, the main context is not synchronized with the private context. (ie: the managed objects in the main context are not in sync with the values contained in notification.userInfo?[NSUpdatedObjectsKey]) If I kill the app and relaunch it, the contexts become synchronized again (after loading objects from the persistent store).

I have -com.apple.CoreData.ConcurrencyDebug 1 enabled in my launch arguments, and all Core Data multithreading rules are being followed. I can't see anything overtly wrong with my managed object context hierarchy or the merging function. What could possibly be causing this?

5
  • Try github.com/magicalpanda/MagicalRecord Commented Feb 9, 2017 at 2:28
  • how are you testing that the managed objects in the main context are not in sync? Commented Feb 14, 2017 at 10:03
  • If you are only supporting iOS 10+ then you could use automaticallyMergesChangesFromParent which will deal with this for you. Commented Feb 14, 2017 at 10:06
  • in the line if savedContext == privateObjectContext { is privateObjectContext the parent of the main context or the sibling? Commented Feb 14, 2017 at 10:15
  • I am testing that they are out of sync by logging the objects (using mainObjectContext.objectWithID(_:)) before and after calling mainObjectContext.mergeChangesFromContextDidSaveNotification(notification). I'm also logging the notification.userInfo?[NSUpdatedObjectsKey] values since those represent the state of the objects in the private context. And privateObjectContext is the sibling of the main context Commented Feb 14, 2017 at 19:10

4 Answers 4

1

I used to use a similar structure as yours, but it wasn't reliable in my case. Sometimes it did work, sometimes it didn't. One of the errors was "incomplete merges", just like you described. I started observing this behavior in iOS 10. I believe something may have change on Core Data's core.

Anyway, I changed my approach. I started using the Apple's sample code on Core Data / Concurrency at:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html#//apple_ref/doc/uid/TP40001075-CH24-SW3

If you read the entire page (it isn't that big), you may notice that they suggest creating a private queue, as usual, but then:

This example can be further simplified when using an NSPersistentContainer:

let jsonArray = … let container = self.persistentContainer container.performBackgroundTask() { (context) in for jsonObject in jsonArray { let mo = EmployeeMO(context: context) mo.populateFromJSON(jsonObject) } do { try context.save() } catch { fatalError("Failure to save context: \(error)") } } 

Of course I'm not sure if the above code fits in your requirements exactly. However the key thing there is to rely on the persistentContainer to do the heavy lifting.

After all of the data has been consumed and turned into NSManagedObject instances, you call save on the private context, which moves all of the changes into the main queue context without blocking the main queue.

Sign up to request clarification or add additional context in comments.

Comments

0

You can't merge sibling context. You have to merge from the parent down. In other words change

 if savedContext == privateObjectContext { 

to

 if savedContext == savingObjectContext { 

where the savingObjectContext is the parent context of the main context.

1 Comment

What makes you say that sibling contexts can't be merged? Is there a piece of documentation you can cite to back that up? This answer: stackoverflow.com/a/24663533 shows two common managed object context hierarchies where data is merged between siblings.
0

You could always set your mainQueueContext as your privateQueueContext's parent:

privateObjectContext.parent = mainObjectContext 

This will merge your saves into your main object context. I used this in a project where I parse JSON, and set core data objects in a privateQueue's perform block, with my mainContext set as the parent. Then I simple save the private, and then access the data from the main thread/main context. Works like a charm. I should add I do not keep the private context in memory, it is created new when I need it.

1 Comment

It's true that this works, but it's non-ideal from a performance standpoint because all data fetching and saving back to the persistent store done from the private object context must be 'pulled'/'pushed' through the main queue context.
-1

The problem is with your merge policy. Try changing to to NSErrorMergePolicy and I think you will start to see merge conflicts. At the least this will give you more of an idea of the underlying cause

1 Comment

I have tried changing the merge policy on all managed object contexts to NSErrorMergePolicy but I'm not encountering any errors when saving from either of the private queue contexts.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.