What does Apple mean when they say that a NSManagedObjectContext is owned by the thread or queue that created it?
The NSManagedObjectContext and any managed objects associated with it should be pinned to a single actor (thread, serialized queue, NSOperationQueue with max concurrency = 1).
This pattern is called thread confinement or isolation. There isn't a great phrase for (thread || serialized queue || NSOperationQueue with max concurrency = 1) so the documentation goes on to say "we'll just use 'thread' for the remainder of the Core Data doc when we mean any of those 3 ways of getting a serialized control flow"
If you create a MOC on one thread, and then use it on another, you have violated thread confinement by exposing the MOC object reference to two threads. Simple. Don't do it. Don't cross the streams.
We call out NSOperation explicitly because unlike threads & GCD, it has this odd issue where -init runs on the thread creating the NSOperation but -main runs on the thread running the NSOperation. It makes sense if you squint at it right, but it is not intuitive. If you create your MOC in -[NSOperation init], then NSOperation will helpfully violate thread confinement before your -main method even runs and you're hosed.
We actively discourage / deprecated using MOCs and threads in any other ways. While theoretically possible to do what bbum mentions, no one ever got that right. Everybody tripped up, forgot a necessary call to -lock in 1 place, "init runs where ?", or otherwise out-clevered themselves. With autorelease pools and the application event loop and the undo manager and cocoa bindings and KVO there are just so many ways for one thread to hold on to a reference to a MOC after you've tried to pass it elsewhere. It is far more difficult than even advanced Cocoa developers imagine until they start debugging. So that's not a very useful API.
The documentation changed to clarify and emphasize the thread confinement pattern as the only sane way to go. You should consider trying to be extra fancy using -lock and -unlock on NSManagedObjectContext to be (a) impossible and (b) de facto deprecated. It's not literally deprecated because the code works as well as it ever did. But your code using it is wrong.
Some people created MOCs on 1 thread, and passed them to another without calling -lock. That was never legal. The thread that created the MOC has always been the default owner of the MOC. This became a more frequent issue for MOCs created on the main thread. Main thread MOCs interact with the application's main event loop for undo, memory management, and some other reasons. On 10.6 and iOS 3, MOCs take more aggressive advantage of being owned by the main thread.
Although queues are not bound to specific threads, if you create a MOC within the context of a queue the right things will happen. Your obligation is to follow the public API.
If the queue is serialized, you may share the MOC with succeeding blocks that run on that queue.
So do not expose an NSManagedObjectContext* to more than one thread (actor, etc) under any circumstance. There is one ambiguity. You may pass the NSNotification* from the didSave notification to another thread's MOC's -mergeChangesFromContextDidSaveNotification: method.
- Ben
Sounds like you had it right. If you're using threads, the thread that wants the context needs to create it. If you're using queues, the queue that wants the context should create it, most likely as the first block to execute on the queue. It sounds like the only confusing part is the bit about NSOperations. I think the confusion there is NSOperations don't provide any guarantee about what underlying thread/queue they run on, so it may not be safe to share a MOC between operations even if they all run on the same NSOperationQueue. An alternative explanation is that it's just confusing documentation.
To sum it up:
- If you're using threads, create the MOC on the thread that wants it
- If you're using GCD, create the MOC in the very first block executed on your serial queue
- If you're using NSOperation, create the MOC inside of the NSOperation and don't share it between operations. This may be a bit paranoid, but NSOperation doesn't guarantee what underlying thread/queue it runs on.
Edit: According to bbum, the only real requirement is access needs to be serialized. This means that you can share a MOC across NSOperations as long as the operations are all added to the same queue, and the queue doesn't allow concurrent operations.