当Apple说NSManagedObjectContext由创建它的线程或队列拥有时,它意味着什么?

时间:2011-01-26 02:21:53

标签: objective-c multithreading cocoa core-data

似乎在11月,Apple更新了NSManagedObjectContext Class ReferenceCore Data Programming Guide文档,明确地将串行GCD调度队列和NSOperationQueues作为同步访问NSManagedObjectContext的可接受机制。但他们的建议似乎含糊不清,可能是矛盾的,我想确保我已经理解它。

以前,公认的智慧似乎只能从创建它的线程访问NSManagedObjectContext,并且使用串行队列进行同步是不够的;虽然串行队列一次只执行一个操作,但这些操作可能会安排在不同的线程上,而MOC不喜欢这样做。

但是现在,从编程指南,我们有:

  

您可以使用线程,串行操作队列或调度队列进行并发。为了简明起见,本文使用“线程”来指代其中任何一个。

到目前为止,这么好(尽管他们对线程和队列的混淆是无益的)。所以我可以安全地使用每个(串行)队列的单个上下文,而不是每个操作/块一个,对吧? Apple甚至在Core Data WWDC会议中对此进行了直观描述。

但是......你在哪里为队列创建上下文?在NSManagedObjectContext文档中,Apple声明:

  

[A context]假设默认所有者是分配它的线程或队列 - 这由调用其init方法的线程决定。因此,您不应该在一个线程上初始化上下文,然后将其传递给另一个线程。

所以现在我们想到NSManagedObjectContext需要知道它的拥有者是谁。我假设这意味着要在队列中执行的第一个操作应该创建MOC并保存对它的引用以供剩余的操作使用。

这是对的吗?我犹豫不决的唯一原因是NSManagedObjectContext文章继续说:

  

相反,您应该传递对持久性存储协调器的引用,并让接收线程/队列创建从该派生协调器派生的新上下文。如果使用NSOperation,则必须在main(用于串行队列)或start(用于并发队列)中创建上下文。

Apple现在似乎将操作与安排执行的队列混为一谈。这是我的头脑,并让我想知道他们是否真的希望你只为每个操作创建一个新的MOC。我错过了什么?

2 个答案:

答案 0 :(得分:64)

NSManagedObjectContext和与之关联的任何托管对象都应固定到单个actor(线程,序列化队列,NSOperationQueue,max concurrency = 1)。

此模式称为线程限制或隔离。没有一个很好的短语(thread || serialized queue || NSOperationQueue with max concurrency = 1)所以文档继续说“我们只是在我们的意思是对核心数据doc的其余部分使用'thread'任何这三种获得序列化控制流的方法“

如果在一个线程上创建MOC,然后在另一个线程上使用它,则通过将MOC对象引用暴露给两个线程来违反线程限制。简单。不要这样做。不要越过溪流。

我们明确地呼吁NSOperation,因为不同于线程& GCD,它有这个奇怪的问题,其中-init在创建NSOperation的线程上运行但是-main在运行NSOperation的线程上运行。如果你眯着眼睛看它是有道理的,但它不直观。如果你在 - [NSOperation init]中创建你的MOC,那么NSOperation将有助于违反线程限制,直到你的-main方法运行并且你已经被软化了。

我们主动阻止/弃用使用MOC和线程的任何其他方式。虽然理论上可以做bbum提到的事情,但没有人能做到这一点。每个人都绊倒了,忘了在一个地方打电话给-lock,“init在哪里跑?”,或以其他方式超越自己。使用自动释放池和应用程序事件循环以及撤消管理器和可可绑定以及KVO,在您尝试将其传递到其他位置之后,一个线程可以通过很多方式保留对MOC的引用。在开始调试之前,它甚至比高级Cocoa开发人员想象的要困难得多。所以这不是一个非常有用的API。

文档改为澄清并强调线程限制模式是唯一理智的方式。您应该考虑使用-lock和-unlock在NSManagedObjectContext上进行额外的尝试,以(a)不可能,(b)事实上已弃用。它并没有被弃用,因为代码的工作原理与以前一样好。但是使用它的代码是错误的。

有些人在1个线程上创建了MOC,并在不调用-lock的情况下将它们传递给另一个线程。那从来都不合法。创建MOC的线程始终是MOC的默认所有者。对于在主线程上创建的MOC,这成为更常见的问题。主线程MOC与应用程序的主事件循环交互,以进行撤消,内存管理和其他一些原因。在10.6和iOS 3上,MOC采取更积极的优势,被主线拥有。

虽然队列没有绑定到特定线程,但如果在队列的上下文中创建MOC,则会发生正确的事情。您的义务是遵循公共API。

如果队列是序列化的,您可以与在该队列上运行的后续块共享MOC。

因此,在任何情况下都不要将NSManagedObjectContext *暴露给多个线程(actor等)。有一个含糊不清的地方。您可以将NSNotification *从didSave通知传递到另一个线程的MOC的-mergeChangesFromContextDidSaveNotification:方法。

答案 1 :(得分:11)

听起来你说得对。如果您正在使用线程,那么需要上下文的线程需要创建它。如果您正在使用队列,那么需要上下文的队列应该创建它,很可能是在队列上执行的第一个块。这听起来像唯一令人困惑的部分是关于NSOperations的一点点。我认为NSOperations的混乱并不能保证它们运行的​​底层线程/队列,因此即使它们都在同一个NSOperationQueue上运行,在操作之间共享MOC也可能不安全。另一种解释是它只是令人困惑的文档。

总结一下:

  • 如果您正在使用线程,请在想要它的线程上创建MOC
  • 如果您正在使用GCD,请在串行队列上执行的第一个块中创建MOC
  • 如果您正在使用NSOperation,请在NSOperation内部创建MOC,不要在操作之间共享。这可能有点偏执,但NSOperation并不保证它运行的底层线程/队列。

编辑:根据bbum,唯一真正的要求是访问需要序列化。这意味着只要操作全部添加到同一队列,并且队列不允许并发操作,您就可以跨NSOperations共享MOC。