performBlock:和performBlockAndWait之间的行为差​​异:?

我在私人队列中创buildNSManagedObjectContext来处理从文件和/或服务中获取的数据更新:

 NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator; 

由于我使用的是私有队列,我并不完全理解performBlock:performBlockAndWait: methods之间的区别…为了执行我的数据更新,我正在执行此操作:

 [privateContext performBlock: ^{ // Parse files and/or call services and parse // their responses // Save context [privateContext save:nil]; dispatch_async(dispatch_get_main_queue(), ^{ // Notify update to user }); }]; 

在这种情况下,我的数据更新是同步的和顺序的,所以我想这是保存上下文的正确位置,对吧? 如果我做错了,我会很感激,如果你让我知道。 另一方面,这个代码是否等同?

 [privateContext performBlockAndWait: ^{ // Parse files and/or call services and parse // their responses // Save context [privateContext save:nil]; }]; // Notify update to user 

再次,我想这是保存上下文的正确位置…两种方法(如果有的话)之间有什么区别?

如果不是执行同步服务调用或文件parsing,我需要执行asynchronous服务调用? 这些数据更新将如何pipe理?

提前致谢

你是正确的,你想要做的任何事情都必须在performBlockperformBlockAndWait内完成。 请注意,保留/释放对于托pipe对象是线程安全的,因此您不必位于其中一个块内就可以在托pipe对象上保留/释放引用计数。

它们都使用同步队列来处理消息,这意味着一次只能执行一个块。 那么,这几乎是事实。 请参阅performBlockAndWait的说明。 无论如何,对MOC的访问将被序列化,使得一次只有一个线程访问MOC。

tl; dr不要担心差异,并始终使用performBlock

事实上的差异

有一些差异。 我相信还有更多的东西,但是这些是我认为最重要的东西。

同步与asynchronous

performBlock是asynchronous的,因为它立即返回,并且在某个未公开的线程中将来某个时候会执行该块。 通过performBlock给予MOC的所有块将按照它们添加的顺序执行。

performBlockAndWait是同步的,因为调用线程将在返回之前等待块执行完毕。 无论块在其他线程中运行,还是在调用线程中运行都不是那么重要,并且是不可信的实现细节。

然而请注意,它可以被实现为“嘿,其他一些线程,运行这个块,我会坐在这里什么也不做,直到你告诉我完成了。 或者,它可以实现为“嘿,核心数据,给我一个锁,阻止所有其他块运行,所以我可以在我自己的线程上运行这个块”。 或者可以用其他方式来实现。 再次,实施细节,可能随时改变。

不过,我会告诉你,最后一次testing它时, performBlockAndWait在调用线程上执行了代码块(意味着上面的第二个选项)。 这只是真正的信息,可以帮助您了解正在发生的事情,不应以任何方式依赖。

重入

performBlock 始终是asynchronous的,因此不可重入。 那么有些人可能会认为它是可重入的,因为你可以在一个被performBlock调用的块内调用它。 但是,如果这样做,所有对performBlock调用将立即返回,直到至less当前正在执行的块完成其工作,块才会执行。

 [moc performBlock:^{ doSomething(); [moc performBlock:^{ doSomethingElse(); }]; doSomeMore(); }]; 

这些函数将始终按以下顺序执行:

 doSomething() doSomeMore() doSomethingElse() 

performBlockAndWait 总是同步的。 而且,它也是可重入的。 多次调用不会造成死锁。 因此,如果最终在由于另一个performBlockAndWait而正在运行的块内部调用performBlockAndWait时,则可以。 你会得到预期的行为,因为第二个调用(和任何后续的调用)不会导致死锁。 此外,第二个将在它返回之前完全执行,就像你所期望的那样。

 [moc performBlockAndWait:^{ doSomething(); [moc performBlockAndWait:^{ doSomethingElse(); }]; doSomeMore(); }]; 

这些函数将始终按以下顺序执行:

 doSomething() doSomethingElse() doSomeMore() 

FIFO

FIFO代表“先入先出”,意思是块将按照进入内部队列的顺序执行。

performBlock始终尊重内部队列的FIFO结构。 每个块将被插入到队列中,并且只有在按照先进先出的顺序被移除时才会运行。

根据定义, performBlockAndWait会中断FIFOsorting,因为它会跳转已经排入队列的队列。

使用performBlockAndWait提交的块不必等待队列中正在运行的其他块。 有很多方法可以看到这一点。 一个简单的是这个。

 [moc performBlock:^{ doSomething(); [moc performBlock:^{ doSomethingElse(); }]; doSomeMore(); [moc performBlockAndWait:^{ doSomethingAfterDoSomethingElse(); }]; doTheLastThing(); }]; 

这些函数将始终按以下顺序执行:

 doSomething() doSomeMore() doSomethingAfterDoSomethingElse() doTheLastThing() doSomethingElse() 

这个例子很明显,这就是我使用它的原因。 但是,考虑一下,如果你的MOC从多个地方获取东西的话。 可能有点混乱。

需要记住的是, performBlockAndWait是抢占式的,可以跳过FIFO队列。

僵局

你永远不会得到一个调用performBlock的死锁。 如果你在块内部做了一些愚蠢的事情,那么你可能会死锁,但是调用performBlock永远不会死锁。 你可以从任何地方调用它,它会简单地将该块添加到队列中,并在将来执行一段时间。

你可以很容易地调用performBlockAndWait调用死锁,尤其是如果你从一个外部实体可以在嵌套的上下文中调用或不加区分的方法调用它。 具体而言,如果父级父母在子级上调用performBlockAndWait ,则几乎可以保证您的应用程序发生死锁。

用户事件

核心数据认为“用户事件”是调用processPendingChanges之间的任何事物。 你可以阅读这个方法为什么重要的细节,但是在“用户事件”中发生的事情对通知,撤消pipe理,删除传播,改变合并等都有影响。

performBlock封装了“用户事件”,这意味着在不同的调用processPendingChanges之间自动执行代码块。

performBlockAndWait不封装“用户事件”。 如果你想要块被视为一个独特的用户事件,你必须自己做。

自动释放池

performBlock在自己的autoreleasepool中封装这个块。

performBlockAdWait不提供唯一的autoreleasepool。 如果你需要一个,你必须自己提供。

个人意见

就我个人而言,我不相信使用performBlockAndWait有很多很好的理由。 我确定有人有一个用其他方式无法完成的用例,但我还没有看到。 如果有人知道这个用例,请与我分享。

最接近的是在父上下文中调用performBlockAndWait (不要在NSMainConcurrencyType MOC上这样做,因为它可能会locking你的UI)。 例如,如果要确保在当前块返回之前数据库已完全保存到磁盘,并且其他块有机会运行。

因此,前段时间,我决定将Core Data视为一个完全asynchronous的API。 因此,我有了大量的核心数据代码,而且在testing之外,我没有一个调用来执行performBlockAndWait

这样的生活好多了。 当我认为“它一定是有用的,或者他们不会提供的”时,我所遇到的问题要less得多。

现在,我已经不再需要performBlockAndWait 。 因此,也许它已经改变了一些,我只是错过了,因为它不再感兴趣我…但我怀疑这一点。