Nirav Bhatt
Nirav Bhatt

Reputation: 6969

iOS: How do Core Data Merge policies affect NSManagedObjectContext Save and Refresh operations

I have been reading about merge policies and it gives me conflicting ideas about when changes are merged.

I am having two contexts - one in background queue and one in main queue. Both have policies defined NSOverwriteMergePolicy which I think is outright wrong. I am already having problems in sync. I often witness that in background sync from server data my NSManagedObjects are often outdated, and I end up saving them, causing loss of data.

Is there any place I could visit all the rules & order of precedence for context save, context refresh with respect to overriding policies?

I have seen all the documentation around merge policies but I am confused whether they take effect upon SAVE or REFRESH. Also, up to some extent, this is very confusing. For example, Apple Docs state this for NSMergeByPropertyObjectTrumpMergePolicy:

A policy that merges conflicts between the persistent store's version of the object and the current in-memory version by individual property, with the external changes trumping in-memory changes. The merge occurs by individual property.

For properties that have been changed in both the external source and in memory, the in-memory changes trump the external ones.

How to ensure that my desired properties get modified / remain unaffected when I choose to modify / not modify them on different contexts?

Upvotes: 0

Views: 666

Answers (1)

Jon Rose
Jon Rose

Reputation: 8563

TL:DR: Merge policy is evil. You should only write to core-data in one synchronous way.

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 will lose data that way. The way that a lot of pros have been dealing with the problem for a long time 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).

Making this setup with NSPersistentContainer is very easy. In your core-data manager create a NSOperationQueue

_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

And do all writing using this queue:

- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
    void (^blockCopy)(NSManagedObjectContext*) = [block copy];

    [self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
        NSManagedObjectContext* context =  self.persistentContainer.newBackgroundContext;
        [context performBlockAndWait:^{
            blockCopy(context);
            [context save:NULL];  //Don't just pass NULL here. look at the error and log it to your analytics service
        }];
    }]];
}

When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.

Upvotes: 1

Related Questions