What does Apple mean when they say that NSManagedObjectContext belongs to the thread or queue that created it?

Apple seems to have updated both the NSManagedObjectContext Reference Reference and Master Data Programming Guide in November to explicitly bless the GCD serial queues and NSOperationQueues as acceptable mechanisms for synchronizing access to NSManagedObjectContext . But their advice seems ambiguous and possibly contradictory, and I I want to make sure I understand correctly.

Previously, generally accepted wisdom was that NSManagedObjectContext could only be obtained from the thread that created it, and that using a sequential queue for synchronization was insufficient; although consecutive queues perform only one operation at a time, these operations can be scheduled on different threads, and MOC does not like it.

But now from the programming guide we have:

You can use threads, queue queues, or send queues for concurrency. For brevity, this article uses the "stream" to refer to any of them.

So far, so good (although combining threads and queues is useless). That way, I can safely use a single context for a (sequential) queue, rather than one per operation / block, right? Apple even has a visual representation of this in WWDC master data sessions.

But ... where do you create the context for the queue? In the NSManagedObjectContext documentation, Apple status:

[Context] assumes that the default owner is the thread or queue that allocates it β€” this is determined by the thread that invokes its init method. Therefore, you should not initialize the context in one thread, and then pass it to another thread.

So now we have the idea of NSManagedObjectContext , which should know who its owner is. I assume that this means that the first operation to be performed in the queue is to create the MOC and keep a reference to it for the rest of the operations that will be used.

Is it correct? The only reason I hesitate is because the NSManagedObjectContext article says:

Instead, you should pass a link to the repository's permanent coordinator and get the receiving thread / queue to create a new context derived from this. If you are using NSOperation, you must create the context mainly (for a sequential queue) or run (for a parallel queue).

Now Apple seems to be combining operations with queues that plan to run them. It makes my head and makes me wonder if they really want you to simply create a new MOC for each operation. What am I missing?

+59
multithreading objective-c cocoa core-data
Jan 26 '11 at 2:21
source share
2 answers

NSManagedObjectContext and any associated managed objects must be bound to a single player (thread, serialized queue, NSOperationQueue with max concurrency = 1).

This pattern is called restriction or isolation of flows. There is no big phrase for (thread || serialized queue || NSOperationQueue with max concurrency = 1), so the documentation goes on to say: β€œWe will use theβ€œ thread ”for the rest of the Core Data document when we mean any of these three methods of getting serialized control flow "

If you create a MOC in one thread and then use it on another, you violated the flow restriction by exposing the MOC object reference to two threads. Simply. Do not do this. Do not cross streams.

We explicitly call NSOperation because, unlike threads and GCD, this problem has such a strange problem when -init works in a thread that creates NSOperation, but -main works in a thread that performs NSOperation. It makes sense if you evaluate it correctly, but it is not intuitive. If you create your MOC in [NSOperation init], NSOperation will effectively violate the flow restriction before your -main method is even started and you are closed.

We actively discourage / refuse to use MOC and flows in any other way. Although it is theoretically possible to do what is mentioned in bbum, no one understood. Everyone stumbled, forgot the right call for -lock in 1st place, "init starts where?" Or otherwise cleaned himself up. With autocomplete pools and an application event loop and undo manager and cocoa and KVO bindings, there are only so many ways that one thread maintains a link to the MOC after you try to pass it to another location. This is much more complicated than even advanced cocoa developers until they start debugging. So not a very useful API.

The documentation has changed to clarify and emphasize the flow restriction pattern as the only normal way. You should consider trying to be extravagant using -lock and -unlock in NSManagedObjectContext to be (a) impossible and (b) de facto deprecated. This is not literally not recommended, because the code works as well as ever. But your code uses it incorrectly.

Some people created MOC on 1 thread and passed them to another without causing -lock. It was never legal. The thread that created the MOC was always the owner of the MOC by default. This has become a more frequent problem for MOCs created in the main thread. The main MOC thread interacts with the main application event loop for cancellation, memory management, and some other reasons. On 10.6 and iOS 3, MOCs take a more proactive advantage in that they belong to the main thread.

Although the queues are not tied to specific threads, if you create the MOC in the context of the queue, everything will be correct. Your responsibility is to follow the public API.

If the queue is serialized, you can share the MOC with subsequent blocks that run in this queue.

Therefore, do not expose NSManagedObjectContext * to more than one thread (actor, etc.) under any circumstances. There is one ambiguity. You can pass NSNotification * from the didSave notification to another MOC stream -mergeChangesFromContextDidSaveNotification: method.

  • Ben
+63
Jan 27 2018-11-11T00:
source share

Looks like you're all right. If you use threads, the thread that wants the context must create it. If you use queues, the queue that wants the context should create it, most likely, as the first block to execute in the queue. It seems the only confusing part is the bit about NSOperations. I think the confusion in NSOperations makes no guarantee what kind of main thread / queue they start, so it is not safe to share the MOC between operations, even if they all work on the same NSOperationQueue. An alternative explanation is that it just confuses the documentation.

Summarizing:

  • If you use threads, create a MOC for the thread that wants it.
  • If you use a GCD, create the MOC in the very first block executed in your sequential queue
  • If you are using NSOperation, create a MOC inside NSOperation and do not share it between operations. It may be a little paranoid, but NSOperation does not guarantee in which thread / queue it is running.

Edit: According to bbum, the only real requirement is the need for serialization of access. This means that you can share the MOC through NSOperations, as long as all operations are added to the same queue, and the queue does not allow simultaneous operations.

+11
Jan 26 '11 at 2:28
source share



All Articles