Why can't Core Data combine data from a private context into a main context?

I have an application using Core Data with the following fairly standard hierarchy of managed entity contexts:

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 observe NSManagedObjectContextDidSaveNotification , which is called by the following function when the private context is saved and merges the changes into 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 works most of the time, but sometimes I find that changes to existing objects in a private context are not combined into the Main context. I cannot understand why - maintaining a private context is successful; an NSManagedObjectContextDidSaveNotification notification is sent; the notification handler is called; notification.userInfo?[NSUpdatedObjectsKey] contains correctly updated objects; but at the end, the main context does not synchronize with the private context. (i.e., managed objects in the main context are not synchronized with the values ​​contained in notification.userInfo?[NSUpdatedObjectsKey] ). If I kill the application and restart it, the contexts are synchronized again (after loading objects from the persistent storage).

I have -com.apple.CoreData.ConcurrencyDebug 1 in my startup arguments, and all Core Data multithreading rules are followed. I do not see anything explicit in my hierarchy in the context of the managed entity or the merge function. What could be the reason for this?

+6
source share
4 answers

You cannot combine a kinship context. You must merge with the parent. In other words, change

  if savedContext == privateObjectContext { 

to

  if savedContext == savingObjectContext { 

where savingObjectContext is the parent context of the main context.

0
source

You can always specify your mainQueueContext as the parent of privateQueueContext:

 privateObjectContext.parent = mainObjectContext 

This will combine your saves into your main object context. I used this in a project where I parse JSON and set the main data objects in the privateQueue runtime, with my main content set as parent. Then I just keep private, and then access the data from the main thread / main context. It works like a charm. I must add that I do not save the private context in memory, it is created new when I need it.

0
source

I used to use a similar structure like yours, but in my case it was unreliable. Sometimes it worked, sometimes it is not. One of the mistakes was "incomplete merging", as you described. I began to observe this behavior in iOS 10. I believe that something can change in the core of Core Data.

In any case, I changed my approach. I started using Apple's sample code for 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 (this is not so much), you may notice that they suggest creating a private queue, as usual, but then:

This example can be further simplified by using the 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 matches your requirements. However, the main thing is to rely on a persistentContainer for heavy lifting.

After all the data has been consumed and turned into an NSManagedObject, you call save in a private context that moves all changes to the main queue context without locking the main queue.

0
source

The problem is your merger policy. Try switching to NSErrorMergePolicy and I think you will start to see merge conflicts. At the very least, it will give you more ideas about the root cause.

-one
source

Source: https://habr.com/ru/post/1014506/


All Articles