Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why might Core Data fail to merge data from private context to main context?

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?

like image 713
stackunderflow Avatar asked Jan 28 '17 02:01

stackunderflow


People also ask

What is core data context?

The DbContext class is an integral part of Entity Framework. An instance of DbContext represents a session with the database which can be used to query and save instances of your entities to a database. DbContext is a combination of the Unit Of Work and Repository patterns.

What is NSManagedObjectContext?

An object space to manipulate and track changes to managed objects.

Why core data is faster?

Core Data is heavily optimized with regards to caching, lazy-loading and memory management. If you use it (with the SQLite store type), especially in conjunction with NSFetchedResultsController, you should get better performance than you could get with SQLite on your own.


1 Answers

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.

like image 75
backslash-f Avatar answered Oct 20 '22 01:10

backslash-f