MFA MFA - 9 months ago 120
iOS Question

NSPersistentContainer concurrency for saving to core data

I've read some blogs on this but I'm still confused on how to use NSPersistentContainer

performBackgroundTask
to create an entity and save it. After creating an instance by calling convenience method
init(context moc: NSManagedObjectContext)
in
performBackgroundTask() { (moc) in }
block if I check
container.viewContext.hasChanges
this returns false and says there's nothing to save, if I call save on
moc
(background MOC created for this block) I get errors like this:

fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ... }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
"NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ...} and new database row = {id = 2; ...}"
)}


So I've failed to get the concurrency working and would really appreciate if someone could explain to me the correct way of using this feature on core data in iOS 10

Answer Source

TL:DR: Your problem is that you are writing in the viewContext and you should treat it as read only.

Full explanation: If an object is changed at the same time from two different contexts core-data doesn't know what to do. You can set a mergePolicy to set which change should win, but that really isn't a good solution, because you can lose data that way. The way that a lot of pros have been dealing with the problem for a long time (even before NSPersistentContainer did it) was to have an operation queue to queue the writes so there is only one write going on at a time, and have another context on the main thread only for reads. This way you never get any merge conflicts. (see https://vimeo.com/89370886 for a great explanation on this setup which is now what NSPersistentContainer does internally).

When you call performBackgroundTask the persistentContainer enqueues that block into an internal serial queue. This ensure that there are no mergeConflicts. But if you write to the viewContext that would defeat the whole point of what the persistentContainer is trying to achieve with performBackgroundTask. Likewise you should tread contexts created with newBackgroundContext as readonly because they will also be outside of the internal writing queue. The documentation on NSPersistentContainer is not great, it doesn't explain that there is an internal queue, but is is explicit that the viewContext is readonly - https://developer.apple.com/reference/coredata/nspersistentcontainer/1640622-viewcontext