Ad

Our DNA is written in Swift
Jump

Multi-Context CoreData

When you start using CoreData for persisting your app data you start out with a single managed object context (MOC). This is how the templates in Xcode are set up if you put a checkmark next to “Use Core Data”.

Using CoreData in conjunction with NSFetchedResultsController greatly simplifies dealing with any sort of list of items which you would display in a table view.

There are two scenarios where you would want to branch out, that is, use multiple managed object contexts: 1) to simplify adding/editing new items and 2) to avoid blocking the UI. In this post I want to review the ways to set up your contexts to get you what you want.

Note: I am wrapping my head around this myself for the very first time. Please notify me via e-mail about errors that I might have made or where I am explaining something incorrectly.

First, let’s review the single-context setup. You need a persistent store coordinator (PSC) to manage talking to the database file on disk. So that this PSC knows how the database is structured you need a model. This model is merged from all model definitions contained in the project and tells CoreData about this DB structure. The PSC is set on the MOC via a property. The first rule to remember: A MOC with a PSC will write to disk if you call its saveContext.

Consider this diagram. Whenever you insert, update or delete an entity in this single MOC then the fetched results controller will be notified of these changes and update its table view contents. This is independent of the saving of the context. You can save as rarely or as often as you want. Apple’s template saves on each addition of an entity and also (curiously) in applicationWillTerminate.

This approach works well for most basic cases, but as I mentioned above there are two problems with it. The first one is related to adding a new entity. You probably want to reuse the same view controller for adding and editing an entity. So you might want to create a new entity even before presenting the VC for it to be filled in. This would cause the update notifications to trigger an update on the fetched results controller, i.e. an empty row would appear shortly before the modal view controller is fully presented for adding or editing.

The second problem would be apparent if the updates accrued before the saveContext are too extensive and the save operation would take longer than 1/60th of a second. Because in this case the user interface would be blocked until the save is done and you’d have a noticeable jump for example while scrolling.

Both problems can be solved by using multiple MOCs.

The “Traditional” Multi-Context Approach

Think of each MOC as being a temporary scratchpad of changes. Before iOS 5 you would listen for changes in other MOCs and merge in the changes from the notification into your main MOC. A typical setup would look like this flow chart:

You would create a temporary MOC for use on a background queue. So allow the changes there to also be persisted you would set the same PSC on the temporary MOC as in the main MOC. Marcus Zarra put it like this:

Although the NSPersistentStoreCoordinator is not thread safe either, the NSManagedObjectContext knows how to lock it properly when in use. Therefore, we can attach as many NSManagedObjectContext objects to a single NSPersistentStoreCoordinator as we want without fear of collision.

Calling saveContext on the background MOC will write the changes into the store file and also trigger a NSManagedObjectContextDidSaveNotification.

In code this would roughly look like this:

dispatch_async(_backgroundQueue, ^{
   // create context for background
   NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
   tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
   // something that takes long
 
   NSError *error;
   if (![tmpContext save:&error])
   {
      // handle error
   }
});

Creating a temporary MOC is very fast, so you don’t have to worry about frequently creating and releasing these temporary MOCs. The point is to set the persistentStoreCoordinator to the same one what we had on the mainMOC so that the writing can occur in the background, too.

I prefer this simplified setup of the CoreData stack:

- (void)_setupCoreDataStack
{
   // setup managed object model
   NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Database" withExtension:@"momd"];
   _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
 
   // setup persistent store coordinator
   NSURL *storeURL = [NSURL fileURLWithPath:[[NSString cachesPath] stringByAppendingPathComponent:@"Database.db"]];
 
   NSError *error = nil;
   _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
 
   if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) 
   {
   	// handle error
   }
 
   // create MOC
   _managedObjectContext = [[NSManagedObjectContext alloc] init];
   [_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
 
   // subscribe to change notifications
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
}

Now please consider the notification handler which we set as target for whenever such a didSave notification arrives.

- (void)_mocDidSaveNotification:(NSNotification *)notification
{
   NSManagedObjectContext *savedContext = [notification object];
 
   // ignore change notifications for the main MOC
   if (_managedObjectContext == savedContext)
   {
      return;
   }
 
   if (_managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
   {
      // that's another database
      return;
   }
 
   dispatch_sync(dispatch_get_main_queue(), ^{
      [_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
   });
}

We want to avoid merging our own changes, hence the first if. Also if we have multiple CoreData DB in the same app we want to avoid trying to merge changes that are meant for another DB. I had this problem in one of my apps which is why I check the PSC. Finally we merge the changes via the provided mergeChangesFromContextDidSaveNotification: method. The notification has a dictionary of all the changes in its payload and this method knows how to integrate them into the MOC.

Passing Managed Objects Between Contexts

It is strictly forbidden to pass a managed object that you have gotten from one MOC to another. There is a simple method to sort of “mirror” a managed object via its ObjectID. This identifier is thread-safe and you can always retrieve it from one instance of an NSManagedObject and then call objectWithID: on the MOC you want to pass it to. The second MOC will then retrieve its own copy of the managed objects to work with.

NSManagedObjectID *userID = user.objectID;
 
// make a temporary MOC
dispatch_async(_backgroundQueue, ^{
   // create context for background
   NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
   tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
   // user for background
   TwitterUser *localUser = [tmpContext objectWithID:userID];
 
   // background work
});

The described approach is fully backwards-compatible all the way down to the first iOS version that introduced CoreData, iOS 3. If you are able to require iOS 5 as deployment target for your app then there is a more modern approach which we shall inspect next.

Parent/Child Contexts

iOS 5 introduced the ability for MOCs to have a parentContext. Calling saveContext pushes the changes from the child context to the parent without the need for resorting to the trick involving merging the contents from a dictionary describing the changes. At the same time Apple added the ability for MOCs to have their own dedicated queue for performing changes synchronously or asynchronously.

The queue concurrency type to use is specified in the new initWithConcurrencyType initializer on NSManagedObjectContext. Note that in this diagram I added multiple child MOCs that all have the same main queue MOC as parent.

Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.

The first necessary change for this approach is to change the main MOC concurrency type to NSMainQueueConcurrencyType. In the above mentioned _setupCoreDataStack the init line changes like shown below and the merge notification is no longer necessary.

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];

A lenghty background operation would look like this:

NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = mainMOC;
 
[temporaryContext performBlock:^{
   // do something that takes some time asynchronously using the temp context
 
   // push to parent
   NSError *error;
   if (![temporaryContext save:&error])
   {
      // handle error
   }
 
   // save parent to disk asynchronously
   [mainMOC performBlock:^{
      NSError *error;
      if (![mainMOC save:&error])
      {
         // handle error
      }
   }];
}];

Each MOC now needs to be used with performBlock: (async) or performBlockAndWait: (sync) to work with. This makes sure that the operations contained in the block are using the correct queue. In the above example the lengthy operation is performed on a background queue. Once this is done and the changes are pushed to the parent via saveContext then there is also an asynchronous performBlock for saving the mainMOC. This again is happening on the correct queue as enforced by performBlock.

Child MOCs don’t get updates from their parents automatically. You could reload them to get the updates but in most cases they are temporary anyway and thus we don’t need to bother. As long as the main queue MOC gets the changes so that fetched results controllers are updated and we get persistence on saving the main MOC.

The awesome simplification afforded by this approach is that you can create a temporary MOC (as child) for any view controller that has a Cancel and a Save button. If you pass a managed object for editing you transfer it (via objectID, see above) to the temp context. The user can update all elements of the managed object. If he presses Save then you save the temporary context. If he presses cancel you don’t have to do anything because the changes are discarded together with the temporary MOC.

Does your head spin by now? If not, then here’s the total apex of CoreData Multi-Context-ness.

Asynchronous Saving

CoreData guru Marcus Zarra has shown me the following approach which builds on the above Parent/Child method but adds an additional context exclusively for writing to disk. As alluded to earlier a lenghty write operation might block the main thread for a short time causing the UI to freeze. This smart approach uncouples the writing into its own private queue and keeps the UI smooth as button.

The setup for CoreData is also quite simple. We only need to move the persistentStoreCoordinator to our new private writer MOC and make the main MOC be a child of this.

// create writer MOC
_privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateWriterContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
 
// create main thread MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = _privateWriterContext;

We now have to do 3 saves for every update: temporary MOC, main UI MOC and for writing it to disk. But just as easy as before we can stack the performBlocks. The user interface stays unblocked during the lengthy database operation (e.g. import of lots of records) as well as when this is written do disk.

Conclusion

iOS 5 greatly simplified dealing with CoreData on background queues and to get changes flowing from child MOCs to their respective parents. If you still have to support iOS 3/4 then these are still out of reach for you. But if you are starting a new project that has iOS 5 as minimum requirement you can immediately design it around the Marcus Zarra Turbo Approach as outlined above.

Zach Waldowski pointed out to me that using a private queue concurrency type for “editing view controllers” might be overkill. If you use NSContainmentConcurrencyType instead on the child view controllers context then you don’t need the performBlock wrapping. You’d still have to performBlock on the mainMOC for saving.

The confinement concurrency type is “the old way” of doing contexts, but that doesn’t mean it’s legacy. It simply ties the operations of the context to a self-managed threading model. Spinning up a private queue for every new view controller is wasteful, unnecessary, and slow. -performBlock: and -performBlockAndWait: don’t work with the confinement concurrency type for a reason, because neither blocks nor locking are necessary when you’re doing multiple contexts in the way that you are in the “editing” view controller setup.

NSManagedObjectContext knows how to save and merge intelligently, and because the main thread context is bound to the main thread, its merges are always performed safely. The editing view controller is bound to the main thread just like the main view controller; the only way it’s a separate operation is just in a UI sense, which is why it’s appropriate to use the confinement concurrency type here. The editing context isn’t conceptually a “new” thing, it’s just deferring the change until later, while still allowing you to discard the changes entirely.

So it really boils down to your personal preference: private queue with performBlock or confinement concurrency without. Personally have a tendency to prefer private queues because of the warm fuzzy and safe feeling I get from using them.


Categories: Recipes

61 Comments »

  1. Thanks for a great article… though I think I will need to re-read it a couple of times for clarity 🙂

    I ended up in my project out of ignorance mostly creating multiple managed object contexts. I was adding CoreData to a project and so I never really saw the template that Xcode provided that shows using a single MOC for the entire app.

    In my app I had multiple datasets that I wanted to represent independently of the core information of the application. For example–I wanted to keep a database of airlines, hotels and rental car companies and i did not want it stored with expense data. So I decided to use multiple object contexts.

    This overall works fine but the downside that I now fear is that when I decide to integrate iCloud that syncing will not occur as it should because each class that I made that used a separate MOC for will not sync as they may not be open long enough for the background sync to occur.

    Anyway… I am not yet working on iCloud integration but when I do I may regret my design.

  2. i have a question for the “- (void)_mocDidSaveNotification:(NSNotification *)notification” method for the 3 last lines of code:

    Isn’t it saver to call the perfomBlock Method to merge the changes in the parent Context, so you perform the task on the correct queue and thread?; like:

    [savedContext performBlock:^{

    [savedContext.parentContext performBlock:^{
    [savedContext.parentContext mergeChangesFromContextDidSaveNotification:nil];
    }];

    }];

  3. Yes, BUT 1) the point was to demonstrate the “old style” of merging without blocks. And 2) you would get an exception because the mainMOC would not have the appropriate concurrency type.

  4. so i should only “mergeChangesFromContextDidSaveNotification: …” for the mainMoc on the main-thread?

    but i thought that the “performBlock” exactly do this. (perform the block on the correct queue in the correct thread)

  5. sry, my fault; if i init the mainMoc with _mainMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; all should be fine…

  6. I don’t see how your asynchronous saving solution [completely] uncouples writing. I get that my background context fetches a lot of data then kicks that up to the main thread, which kicks it up to a background thread for saving. But if my main thread makes any other query while the save is ongoing then surely the store is still locked so the main queue is still blocked? How is that a better position than the iOS 4-style approach where the background would complete a save before the main queue’s context merged in?

  7. This is a great article and helped me a LOT! Other info in forums are just too bits and pieces.

  8. Thanks for the great article. However, like Tom, I was wondering if the main context will not be blocked whenever you perform a save to disk with the persistent store. I am using the last design pattern, and I keep running into my app locking up. I have done some more searching and came across this article: http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains. Perhaps you can comment?

  9. Just published Marcus Zarra’s answer on the Locking question: http://www.cocoanetics.com/2013/02/zarra-on-locking/

  10. Excellent article and insight – many thanks.

    I am at the stage where I have a data based, multi-user app up and running on my simulator and I need to move it up to the Internet (not iCloud) so that is can perform as it should. So obviously I have all challenges to deal with that this model creates.

    Looking at the parent-child MOC, I believe, should help solve many of the issues.

    e.g.

    Right now, the main app screen, is a TVC populated by a FetchRequest.
    Main issue with this are :
    – if slow connection would block main thread
    – MOC is not thread safe

    So I plan to :

    In the MAIN thread :
    – create the mainMOC
    – have a FetchRequest against the mainMOC that fetches all records in the mainMOC

    In a new thread :
    – create a newMOC
    – create the FetchRequest I want to select a subset of all the records on the CoreData store
    – when this FetchRequest completes
    – push the newMOC up to the mainMOC

    Does this sound like the correct approach ? Any advise would be much appreciated.

    Thanks,
    Guy

  11. OK I tried what I said I was going to try and it looks like it is working.

    The only issue is that all the threads complete after the TVC has loaded, so once the threads complete I need to refresh the TVC.

    Guy

  12. There are several reports of multiple managed object contexts (parent/child) having issues with fetched properties.
    Just did a refactoring of a project and to our surprise, fetched properties stopped working.

    See similar issues:
    https://devforums.apple.com/message/804899#804899

  13. Have to say this helped me immensely. Thanks for putting this together.

  14. Thanks for article.
    I think iOS 5 concurrency model is good improvement. But when you are making “Asynchronous Saving” you may get some logic error. Example: when you are saving some data in “temporary” background MOCs you should call save main thread MOC and then “master” MOC. If a view controller saves any changes in main thead MOC – it automatically saves changes of main thread MOC’s children – which have been saved their changes but didn’t call main thread save block yet. For example, they may not call main MOC save for rollback reasons…

  15. Great tutorial to understand threaded Core Data concurrency, however I have a doubt that,
    –> Where should I define this CoreDataMngr class such that the function – (void)_setupCoreDataStack is global to all and initialized only once ? My requirements is that, I need to access Core Data functionality from many Classes, so should I make this CoreDataMngr as a Singelton class.

    Thank for replying.

  16. I added a more concrete example that compiles in Xcode 5.1 here:

    https://github.com/skandragon/CoreDataMultipleContexts

  17. Note that awakeFromInsert is no longer called once if you begin using nested contexts. @skandragon’s sample code is also wonderful. Thanks both!

  18. I look at these complicated diagrams and don’t understand why not to use only one context on a background thread. I think you shouldn’t work with entities on the main thread. Just fetch entities, convert them in your objects an pass through notification. Yes you will not be able to use NSFetchedResultsController but you can solve this problem using notifications. This solution is very easy and gives you a great speed because there are no fetch requests on the main thread.

  19. Great article, especially “Asynchronous Saving” part.

  20. hey great article. Very informative, but I’m having trouble wrapping my head around where the pieces actually go. Do you have an example project to help illustrate your parent/child example?

  21. This is a GREAT article and helped me a lot. Thank you.

    FYI, search for the typo NSMangedObjectContext

  22. Hi, Iam getting context changed error when try to change new viewcontroller which access coredata. Is it because multiple context issue? I am so confused,, pls help me..

  23. These changes will reflect your company’s complete product and service presentation. This has naturally created some design challenges; the resolution to such challenges has been to tweak existing techniques to work again. Product videos can be very useful in showing potential
    customers exactly how to use the product you are selling
    or to show the benefits of the product.

  24. Any person on earth with web-based access can be linked to the site.

    Obviously you’re also going to need to possess the necessary
    equipment, as well as for something similar to this that will mean a
    pc and also the right web creating software for example Expensive or perhaps a similar program.
    Successful web sites are “user friendly,” allowing valuable information to
    be obtained easily by the user.

  25. I read on this blog that performance-wise, the “using the same persistentStoreCoordinator on all childContexts” has a much better performance than the child/parent or a private-main-private queue stack of contexts.

    http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/

    I still don’t know which one to use and when?