A while ago I recommended not to multithread at all with Core Data. That was true during Snow Leopard’s days. But with the advent of context hierarchy and the pervasive use of blocks on Lion and iOS 5, multi-threading with Core Data becomes approachable.
As I was working on Resonate, I discovered a number of lessons on writing multithreaded applications on iOS. A lot of these lessons were discovered “the hard way” – through misguided attempts and many crashes. I’d thought I share this with you so that you don’t need to go through the same mistakes.
Even though I use the word “multithreading”, chances are you won’t need to deal with threads directly. Using operation queues is the way to go for most of your multiprocessing mechanisms. Simply package your algorithms into distinct units of work and then let
NSOperationQueue manage them for you. Don’t create
NSThread instances directly unless you have a pretty damn good reason why.
UIManagedDocument for Core Data apps on iOS.
Really, when you’re writing iOS 5 applications that uses Core Data, you’ll want to use
UIManagedDocument. At a bare minimum, use this class to manage your Core Data stack. That is, let core data manage your instances of
NSPersistentStoreCoordinator, and other parts of the Core Data stack.
Even if you need to share your Core Data classes with your Mac app, you can still separate your entity classes that will work accross both platforms. If you think that
UIManagedDocument isn’t good enough for your app, you’d better have a damn good reason why.
When you use
UIManagedDocument, you’ll get a set of interrelated
NSManagedObjectContext instances. A main context that you use as per normal with your GUI objects and the root context, which is the main context’s parent that are used for asynchronously saving data to persistent storage. When dealing with object contexts created by
UIManagedDocument you should not save them directly – you’ll bypass undo management and a lot of other behind-the-scenes stuff.
UIManagedDocument will periodically save these for you in the correct order.
Use the managed object context hierarchy
From this building block, I go further to recommend a pattern for using the new context hierarchy. I discovered this works nicely on iOS when I was working on Resonate.
- Use the root context for saving data obtained by network refreshes.
- Use the main context for GUI components and others that needs to be in the main thread.
- Create individual worker contexts as children of the main context for background data processing tasks.
Use the Root Context for Network Refreshes
Being a Twitter client, Resonate often need to ask Twitter for the latest tweets and other data. When Twitter returns and Resonate parsed the resulting JSON data, it then invokes the root context’s operation queue to store the parsed JSON data into managed objects.
You can use this pattern yourself. You run network operations asynchronously, parse the resulting data, and then when you’re ready to store it you call performBlock on the root context.
You can see the pattern in the sequence diagram below. Typically the user initiates the refresh from an action in the view controller. If you use
ASIHTTPRequest for your network I/O, you can make it run asynchronously. When the request is complete, you pass the raw data into an operation queue for parsing (note that
+[ASIHTTPRequest sharedQueue] is convenient for this). When the data is parsed, you make use of the root context’s private queue to update the data to your managed objects. Upon completion of the update, you pass the object IDs of the updated objects back to your view controller in the main thread. Please take care not to pass the actual managed object instances between operation queues as this will certainly cause problems.
One caveat of this approach is that you need to separate network-sourced data with user-edited data. That is attributes that can be updated from the network should not be editable by the user. This is to prevent conflicts when
UIManagedDocument tries to saves your contexts.
Use Multiple Child Contexts for Worker Queues
If you need to do some long-running data processing, you should run those in a background operation queue and not in the main thread. But really to make your app responsive, anything that may take longer than half a second should be taken off the main thread – especially important in iOS devices where the CPU is a lot slower than the one found in OS X machines.
Those background operations should use their own private
NSManagedObjectContext instances. I also recommend that these contexts are set as children of the main context. Why? Because when the operation completes and the context is saved then the results are “pushed” into the main context. This also rescues the GUI from having to refresh its
NSManagedObjectContext to get the updated objects. Since you create these contexts yourself, you are responsible for saving them – unlike the main and root contexts that are owned by
How to use it? Refer to the interaction diagram below. Typically the view controller creates the worker operation class (which is an instance of
NSOperation). As part of the worker’s initial setup regime, the view controller provides its context as the parent context for use by the worker object. Then when the worker starts it creates its own private
NSManagedObjectContext instance and use that context as it’s scratchpad data store. Just before the worker completes, it saves the context to push its changes to the main thread’s context.
You now know how to multi-thread effectively in Core Data applications. Although this article focuses on iOS, you should be able to apply the same principles to OS X. You have learned about:
UIManagedDocumentfor managing your Core Data stack
- The new
NSManagedObjectContexthierarchy (nested contexts)
- How use each level in the context hierarchy for different operation types (network refresh, main/GUI thread, worker operation).
Until next time, bye now.