Core Data Stack #2 – multiple contexts and resolving conflicts

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/ChangeManagement.html
http://stackoverflow.com/questions/4800889/what-does-apple-mean-when-they-say-that-a-nsmanagedobjectcontext-is-owned-by-the

Multiple Contexts manipulating the Database

source download

Let’s see what happens when we have 2 contexts making changes to the database. First let’s add in an extra NSManagedObjectContext called privateQueueContext2. Implement the custom set method and create our context to execute tasks on a queue.

Then synthesize it and create the custom set method.

CoreDataStack.m

How we want to test it

We’re going to test what happens when 2 contexts edit a database by having the first context get the first Person and change its name to “Ricky”, then save the context. Then we’re gunna have the second context get the first Person and change its name to “Shirley”. The change, and its related saves will be overlapped.

But first, let’s implement the code where the contexts edit a common object (0-th indexed object of the fetched result array), then do a context save.

We do this for private context queue 1:
1) We have changeFirstPersonPrivateQueue method which responds to a button that changes the 0th Person object’s name attribute to Ricky.
2) Then we have privateQueueContextOneSave method which responds to a button that wants context 1 to save.

I want to emphasize that

We do this for private context queue 2:
1) We have changeFirstPersonPrivateQueue2 method which responds to a button that changes the 0th Person object’s name attribute to Shirley.
2) Then we have privateQueueContextTwoSave method which responds to a button that does context 2 save.

Now that the implementation is ready hook up these methods to buttons.

Specifically what we’re trying to do is

to have context 1 be able to make a change to the first Object, then save it.
to have context 2 be able to make a change to the first Object, then save it.

Hence we should have 4 buttons.

2contexts-view

Now, we know if one context makes “changes and save immediately”, then it gets reflected in the database. The next context that either reads or make further changes will see the updates. However, what if the 2 contexts’s changes and saves overlap each other?

Let’s look at this example:

  • First, click on the “add bulk” button to add Person objects into Core Data.
  • Then, click on the “Display All” button to show all the first/last name of the Person objects.
  • Then, we use context 1 to change the 1st Person object’s first name to “Ricky”. Click the “Context1, make change to Ricky” button to edit Object 1’s name to “Ricky”.
  • Then, we use context 2 to change the 1st Person object’s first name to “Shirley”. Click the “Context2, make change to Shirley” button to edit Object 1’s name to “Shirley”.
  • Then click on “Context 2 save”
  • Then click on “Context 1 save”
  • In this case, you’ll get an error:


    2015-10-27 00:29:56.801 CoreData2[48750:5178513] Unresolved error Error Domain=NSCocoaErrorDomain Code=133020 “(null)” UserInfo={conflictList=(
    “NSMergeConflict (0x7f96bbc0f100) for NSManagedObject (0x7f96bbd0aa90) with objectID ‘0xd000000000040000 ‘ with oldVersion = 1 and newVersion = 2 and old object snapshot = {\n firstName = \”remark: 1445930979.058863\”;\n lastName = \”1445930979.058863\”;\n} and new cached row = {\n firstName = SHIRLEY;\n lastName = \”1445930979.058863\”;\n}”
    )}, {
    conflictList = (
    “NSMergeConflict (0x7f96bbc0f100) for NSManagedObject (0x7f96bbd0aa90) with objectID ‘0xd000000000040000 ‘ with oldVersion = 1 and newVersion = 2 and old object snapshot = {\n firstName = \”remark: 1445930979.058863\”;\n lastName = \”1445930979.058863\”;\n} and new cached row = {\n firstName = SHIRLEY;\n lastName = \”1445930979.058863\”;\n}”
    );
    }

    ..and you’ll come to the abort method:

    ref – https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/ChangeManagement.html

    If your application contains more than one managed object context and you allow objects to be modified in more than one context, you need to be able to reconcile the changes. This is a fairly common situation when an application is importing data from a network and the user can also edit that data.

    Ultimately there can be only one truth, and differences between these views must be detected and reconciled when data is saved. When one of the managed object contexts is saved, its changes are pushed through the persistent store coordinator to the persistent store. When the second managed object context is saved, conflicts are detected using a mechanism called optimistic locking. How the conflicts are resolved depends on how you have configured the context.

    When Core Data fetches an object from a persistent store, it takes a snapshot of its state. A snapshot is a dictionary of an object’s persistent properties—typically all its attributes and the global IDs of any objects to which it has a to-one relationship. Snapshots participate in optimistic locking. When the framework saves, it compares the values in each edited object’s snapshot with the then-current corresponding values in the persistent store.

    • If the values are the same, then the store has not been changed since the object was fetched, so the save proceeds normally. As part of the save operation, the snapshots’ values are updated to match the saved data.
    • If the values differ, then the store has been changed since the object was fetched or last saved; this represents an optimistic locking failure. The conflict must be resolved.

    Say in our example, when our contexts gets the first object in our Core Data, it does so using

    as you can see in the code. Basically, the NSArray you get back is a snapshot of its state. Within the NSArray, we get the 0-th index object, and change its attribute. What we’re changing here is just a snapshot. Now, when you decide to save after you change the attribute in the situation of the 2nd context changing the name to Shirley, it compares the values in its object’s snapshot with the then-current corresponding values in the persistent store.

    Context 2 – no conflict

    In our case for context 2, before it saves, it sees that the current corresponding value in the persistent store is the same as the values in the snapshot it received. In both cases, it is the default value “remark: whatever today’s date is”. Since they are the same, then the store has not been changed by other context(s) since context 2 fetched it, and thus the save proceeds normally.

    Context 1 – conflict

    It does not work for context 1 because when context 1 fetches the object, its snapshot is “remark: whatever today’s date is”.

    However, because context 2 previously managed to change the 0th Person object’s name attribute to “Shirley” and then save. This means the current corresponding values in the persistent store is Shirley.

    This means the values differ, and thus, for context 1, the store has been changed since the object was fetched or last saved. This represents an optimistic locking failure. And the conflict must be resolved.

    Resolving conflicts by synchronizing changes between our contexts

    If you use more than one managed object context in an application, Core Data does not automatically notify one context of changes made to objects in another. In general, this is because a context is intended to be a scratch pad where you can make changes to objects in isolation, and you can discard the changes without affecting other contexts. If you do need to synchronize changes between contexts, how a change should be handled depends on the user-visible semantics you want in the second context, and on the state of the objects in the second context.

    Consider an application with two managed object contexts and a single persistent store coordinator. If a user deletes an object in the first context (moc1), you may need to inform the second context (moc2) that an object has been deleted. In all cases, moc1 automatically posts an NSManagedObjectContextDidSaveNotification notification via the NSNotificationCenter that your application should register for and use as the trigger for whatever actions it needs to take. This notification contains information not only about deleted objects, but also about changed objects. You need to handle these changes because they may be the result of the delete. Most of these types of changes involve transient relationships or fetched properties.

    Code for Context 1

    First, let’s look at the context setter methods. Whenever other contexts save, we want to make sure our own context updates on their saves. Thus we add an addObserver to observe saves from other contexts. Whenever other contexts save, the message NSManagedObjectContextDidSaveNotification gets sent, and we capture it like so:

    What this means is that whenever object self.privateQueueContext2 sends a message name NSManagedObjectContextDidSaveNotification, then let’s call custom method mergeChangesFromContext2.

    Note that for the object parameter, if you can it to the sender of the notification such as self.privateQueueContext2, then we will then only be notified of self.privateQueueContext2’s events.

    If we set to “nil” you will get all notification of this type (regardless who sent them).

    In our particular case, we set the object parameter to context 2, so that we can be updated on whatever context 2 just did, or is doing.

    The custom method gets called whenever context 2 saves, because we as context 1, wants to merge changes from context 2 if and when context 2 does save. We do a simple check that if the notification is from context 2, then we simply have our context 1 merge via the method mergeChangesFromContextDidSaveNotification method:

    Run the program, add your bulk of People object. Then Display them. You’ll see the first object which is 0th index.

    reportOnAllPeopleToLog method called
    2015-10-27 11:17:59.555 CoreData2[49227:5232602] ———— RESULTS FROM DATABASE ————
    2015-10-27 11:17:59.556 CoreData2[49227:5232602] There are 10 number of entries so far
    2015-10-27 11:17:59.556 CoreData2[49227:5232602] 0 —————-
    2015-10-27 11:17:59.556 CoreData2[49227:5232602] firstName = remark: 1445969859.502338
    2015-10-27 11:17:59.557 CoreData2[49227:5232602] lastName = 1445969859.502338
    2015-10-27 11:17:59.557 CoreData2[49227:5232602] object address is: 0x7fd358d8f710
    2015-10-27 11:17:59.558 CoreData2[49227:5232602] 1 —————-
    2015-10-27 11:17:59.558 CoreData2[49227:5232602] firstName = remark: 1445969859.822105
    2015-10-27 11:17:59.558 CoreData2[49227:5232602] lastName = 1445969859.822105
    2015-10-27 11:17:59.559 CoreData2[49227:5232602] object address is: 0x7fd358d83d40


    etc.

    Press button Context 1, Change to Ricky
    Press button Context 2, Change to Shirley
    Press Context 2 save
    Press Context 1 save.

    You’ll then see that Context’s RICKY is the latest save

    result:


    reportOnAllPeopleToLog method called
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] ———— RESULTS FROM DATABASE ————
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] There are 10 number of entries so far
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] 0 —————-
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] firstName = RICKY
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] lastName = 1445969859.502338
    2015-10-27 11:19:53.481 CoreData2[49227:5232602] object address is: 0x7fd358c31a90
    2015-10-27 11:19:53.482 CoreData2[49227:5232602] 1 —————-


    etc

    The result is now correct. When context 2 saved Shirley, context 1 merged itself with context 2’s changes, and thus when our context 1 saves, it will see that the persistent store value and its snapshot value are both “Shirley”, and thus goes on to save its own changes of “Ricky”. Thus, after the save, we see that the value “Ricky” is reflected in the database.

    Some Upkeep

    However, this only applies for context 1 merging if and when context 2 saves. What if context 2 need to merge on what context 1 does? That means we have to do the same thing for context 2.

    Also, what if we have x number of contexts? That means for whatever context n that’s saving, we just make sure all other contexts merge with the saving context.

    Notice object is set to nil in order to observe NSManagedObjectContextDidSaveNotification messages from any object. Then in our mergeChanges custom method, we simply make all contexts merge with the saving context.

    and…

    Using the Queue and performing your edits and saves via blocks

    Apple states:
    [A context] assumes the default owner is the thread or queue that allocated it—this is determined by the queue that calls its init method. You should not, therefore, initialize a context on one queue then pass it to a different queue.

    This means that in our custom setter method for a context, we create the context with an init to a concurrency queue type. This means default owner of privateQueueContext is the queue used via the init:

    Hence privateQueueContext and the init-ed queue are now pinned together. This pattern is thread confinement or isolation.

    We created privateQueueContext with this queue, and must remember to always use privateQueueContext with this queue.

    For example, say we have a task: “use privateQueueContext to get a Person object to manipulate and later save into Core Data”. We want to queue this task because we’re doing this 10,000 times and needs to be run on a background. We queue this task on privateQueueContext because privateQueueContext is what is involved.

    Notice we use privateQueueContext on all. If in the task of fetching the Object using privateQueueContext but queueing the task on say privateQueueContext2 (which has its own queue via an init), then we have “have violated thread confinement by exposing the MOC object reference to two queues. Simple. Don’t do it. Don’t cross the streams.” (http://stackoverflow.com/questions/4800889/what-does-apple-mean-when-they-say-that-a-nsmanagedobjectcontext-is-owned-by-the)

    Part of the reason is because multiple queues process tasks concurrently. So whatever context data you are executing in queue A, may also be executed in queue B at the same time. If you were to confine tasks to its attached queue, then no context data will ever be shared because each task is processed one after another via FIFO.

    furthermore