OS X 10.6 Snow Leopard quietly brought with it a new feature for the standard Cocoa document system: Concurrent Document Opening.
It’s pretty nifty, but the NSPersistentDocument docs make no mention of support for it (admittedly they don’t disavow it either), and I suspect it wouldn’t work quite right if you just tried to blindly turn it on. Or, like us, you may be using Core Data in a document-based app, but not using NSPersistentDocument at all. Fortunately there’s only a few things to bear in mind:
Disable undo registration
Several times, the docs make the point that you should disable undo registration while reading. Apple advise it be done for all documents, but especially those that support concurrent reading. Why the doc system doesn’t just do that for you I don’t know…
It’s important to remember that re-enabling undo registration at the end of reading is not enough with Core Data. Make sure you first call -processPendingChanges, or post an undo manager checkpoint notification. They force the context to register any pending operations while the undo manager still knows to ignore them.
Contexts “belong” to a thread or queue
As of OS X 10.7, managed object contexts can be created with a concurrency type. NSDocumentController is going to create a document instance and ask it to read from a background thread. I expect that your managed object context will be created as part of one of these steps. So you need to make sure the context is initialized to use NSMainQueueConcurrencyType.
NSPersistentDocument usually takes care of creating the context for you. You’ll want to create your own context and then swap it out by calling -setManagedObjectContext:.
On 10.6, we don’t have luxury of concurrency types (ooh, back in ma’ day!), so the only way to associate the context with the main thread is to create it there. Happily Daniel Tull and I have a shim for simulating concurrency types on older OS releases.
Load the persistent store
We use the binary store type in Sandvox. Sampling showed that most of the cost of setting up the Core Data stack is the reading of the store file from disk, rather than dealing with the context itself. NSPersistentStoreCoordinator is documented to be pretty comfortable with multithreading, so just allow the persistent store to be added to it on the worker thread and you should be good to go.
As I’m sure we all know by now, AppKit in general should only be used from the main thread. Make sure you play by the document system’s rules and only trigger UI for the document when asked to by methods like ‑makeDocumentWindowControllers and -showWindows. NSDocumentController will take care of calling them on the main thread after the document has been instantiated and read.
Reading from the context
Finally, the only controversial bit of the discussion:
Having set up the Core Data stack, perhaps your app needs to do something more with it. In our case, we fetch managed objects that represent media and the site, and restore some document-wide UI settings. You could ping over to the main thread using -performBlockAndWait: (happily the aforementioned shim implements this for you). But in our case, that code can still take a few seconds to run as it intermingles Core Data work and disk access.
- Access to the context is bracketed with -lock and -unlock calls
- I guarantee no other application code is going to attempt to access the context or its objects in any way at all while this is happening
The first is obviously easy to implement, and I am confident that the document system combined with my UI rule above ensures the second.
I am sure many people will disagree with me, but I haven’t found any nastiness from this during beta testing yet. If I do, this post will be updated to note the problem, I promise!