7
votes

I have the following Core Data setup in my app:

Persistent Store Coordinator
  ^ Background MOC (NSPrivateQueueConcurrencyType)
      ^ Main Queue MOC (NSMainQueueConcurrencyType)

Here is the initialization code:

_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundContext setPersistentStoreCoordinator:self.coordinator];
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainContext setParentContext:_backgroundContext];

I use the background MOC for importing large amounts of data. I also use it to perform complex fetch requests in the background and then pass the object IDs to the main queue to fetch the objects using these IDs.

This works quite well. However, I am not sure how to let the main queue MOC know about the changes made in the background MOC. I know that if I perform a fetch request on the main queue MOC, it will get the changes, but that's not what I want.

Is it OK to use the NSManagedObjectContextObjectsDidChangeNotification notification posted by the background MOC and call mergeChangesFromContextDidSaveNotification: on the main queue MOC? This should then cause the NSManagedObjectContextObjectsDidChangeNotification notification of the main queue MOC to fire. I am listening for this notification in my view controllers and examine the userInfo for changes and redisplay data accordingly. I think you usually do it this way if you have one persistent store coordinator with two attached MOCs. But I am not sure if it is the right way to do, when you have child/parent contexts.

2

2 Answers

6
votes

Having the main MOC use a private parent MOC for asynchronous I/O is fine. However, you should not use that parent MOC for anything but performing background work on behalf of the main MOC. There are many reasons for this (among them performance and nasty issues related to transient object IDs).

If you want to do background updating of the store, here is what I suggest.

PSC <--+-- PrivateMOC <---- MainMOC
       |
       +-- BackgroundPrivateMOC

This will allow background operation that causes the least interruption to the main MOC, while allowing the PSC caches to be shared.

Now, for sharing data...

The MainMOC should listen for and merge DidSave notifications from the BackgroundPrivateMO.

The BackgroundMOC can listen for and merge DidSave notifications from the PrivateMOC.

This allows merging to use only permanent object IDs and optimizes performance.

3
votes

I'd say that listening to NSManagedObjectContextObjectsDidChangeNotification notification is not probably the best solution.

The way I do it and it works is following. Here is main context creation:

_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_mainContext.persistentStoreCoordinator = _persistentStoreCoordinator;

Here is background context creation:

_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_backgroundContext.parentContext = self.mainContext;

Now, background context is only for writing (or reading) objects (may be in background thread). Main context is only for reading from the main queue. Save on background context should look like:

__block BOOL saved = [_backgroundContext save:error];
if (saved && _backgroundContext.parentContext) {
    [_backgroundContext.parentContext performBlockAndWait:^{
        saved = [self.parentContext save:error];
    }];
}

This save method guarantees that all changes will be propagated to main context. If you do a lot of work in many background threads get more familiar with performBlockAndWait: method, which provides mutual exclusion on context.

If you want to be notified about objects' changes, you don't have to listen for notification, you can simply setup NSFetchedResultsController and register as its delegate.