来自文档:
当isFinished方法返回的值更改为YES时,将执行您提供的完成块 . 因此,在操作的主要任务完成或取消之后,操作对象执行该块 .
我正在使用 RestKit/AFNetworking
,如果这很重要的话 .
我在 OperationQueue
中的 NSOperation
中有多个依赖项 . 我使用完成块来设置我的孩子需要的一些变量(将结果附加到数组) .
(task1,...,taskN) - > taskA
taskA addDependency:task1-taskN
taskA是否会收到不完整的数据,因为孩子可以在完成块被触发之前执行?
Reference
Do NSOperations and their completionBlocks run concurrently?
我通过在完成块中添加一个睡眠进行了简单的测试,结果不一样 . 完成块在主线程中运行 . 当所有完成块都处于休眠状态时,子任务就会运行 .
2 回答
正如我在下面的“一些观察”中所讨论的那样,您无法保证在您的其他各种AFNetworking完成块完成之前,此最终相关操作不会启动 . 令我感到震惊的是,如果这个最终操作真的需要等待这些完成块完成,那么你有几个选择:
在完成块的每一个内使用信号量,以便在完成时发出信号并完成操作等待n个信号;要么
不要预先排队这个最终操作,而是让你的个人上传完成块跟踪有多少待处理的上传仍然不完整,当它下降到零时,然后启动最后的“发布”操作 .
正如您在评论中指出的那样,您可以在自己的操作中包装AFNetworking操作及其完成处理程序的调用,然后您可以使用标准的
addDependency
机制 .您可以放弃
addDependency
方法(在此操作所依赖的操作的isFinished
键上添加观察者,并且一旦所有这些依赖关系得到解决,执行isReady
KVN;问题在于理论上这可能在您完成之前发生阻止完成)并用您自己的isReady
逻辑替换它 . 例如,假设您有一个post操作,您可以添加自己的密钥依赖项并在完成块中手动删除它们,而不是在isFinished
时自动删除它们 . 这样,你自定义操作然后,您的应用程序代码可以执行类似的操作,使用
addKeyDependency
而不是addDependency
,并在完成块中显式显示removeKeyDependency
或cancel
:这是使用
AFHTTPRequestOperation
,您显然会使用适当的AFNetworking操作替换所有这些逻辑,但希望它能说明这个想法 .原始答案:
一些观察:
maxConcurrentOperationCount
而尚未启动的操作,或者由于操作之间的依赖性) . 我不相信您确信在下一次操作开始之前完成块将完成 .根据经验,看起来依赖操作直到完成块完成后才会实际触发,但是(a)我没有使用AFNetworking自己的
setCompletionBlockWithSuccess
,它最终将块异步调度到主队列(或定义的successCallbackQueue
)从而阻止任何(无证)同步保证 .NSOperation
完成块,你没有这样的保证 . 事实上,setCompletionBlock
documentation says:但是如果你自定义完成块,例如那些你可能用
AFHTTPRequestOperation
设置的setCompletionBlockWithSuccess
,然后,是的,这是真的那些通常被派遣回主队列 . 但AFNetworking使用标准completionBlock
机制来做到这一点,因此上述问题仍然适用 .如果你的
NSOperation
是AFHTTPRequestOperation的子类,这很重要 . AFHTTPRequestOperation在方法setCompletionBlockWithSuccess:failure
中将NSOperation
的属性completionBlock
用于其自身目的 . 在这种情况下,请不要自己设置属性completionBlock
!看来,AFHTTPRequestOperation的成功和失败处理程序将在主线程上运行 .
否则,
NSOperation
的完成块的执行上下文是"undefined" . 这意味着,完成块可以在任何线程/队列上执行 . 实际上它在某个私有队列上执行 .IMO,这是首选方法,除非执行上下文应由调用站点明确指定 . 在线程或队列上执行完成处理程序可以访问哪些实例(例如主线程)很容易导致不谨慎的开发人员死锁 .
编辑:
如果要在完成父操作的完成块之后启动依赖操作,可以通过使完成块内容本身为
NSBlockOperation
(新父节点)来解决此问题,并将此操作作为依赖项添加到子操作和在队列中启动它 . 你可能会意识到,这很快变得笨拙 .另一种方法需要实用程序类或类库,它特别适合以更简洁和简单的方式解决异步问题 . ReactiveCocoa将能够解决这样一个(一个简单的)问题 . 但是,除非你同意花几周时间学习它并且有很多其他异步用例甚至更复杂的用例,否则它会推荐它 .
一种更简单的方法将使用“Promises”,这在JavaScript,Python,Scala和一些其他语言中非常常见 .
现在,请仔细阅读,(简单)解决方案实际上如下:
"Promises"(有时称为Futures或Deferred)表示异步任务的最终结果 . 您的提取请求是这样的异步任务 . 但是,相反指定完成处理程序,异步方法/任务返回一个Promise:
您可以通过注册成功处理程序块或失败处理程序块来获得结果 - 或错误 - 如下所示:
或者,内联块:
而且更短:
这里,
parseAsync:
是一个返回Promise的异步方法 . (是的,承诺) .您可能想知道如何从解析器中获取结果?
这实际上启动了异步任务
fetchThingsWithURL:
. 然后,当成功完成后,它将启动异步任务parseAsync:
. 然后,当成功完成时,它会打印结果,否则会输出错误 .依次调用几个异步任务,一个接一个地称为“continuation”或“chaining” .
请注意,上面的整个语句是异步的!也就是说,当您将上述语句包装到方法中并执行它时,该方法立即返回 .
您可能想知道如何捕获任何错误,例如
fetchThingsWithURL:
失败或parseAsync:
:处理程序在相应的任务完成后执行(当然) . 如果任务成功,将调用成功处理程序(如果有) . 如果任务失败,将调用错误处理程序(如果有) .
处理程序可以返回Promise(或任何其他对象) . 例如,如果异步任务成功完成,则将调用其成功处理程序,该处理程序将启动另一个异步任务,该任务将返回promise . 当这个完成后,又可以启动另一个,所以强行 . 那是"continuation";)
您可以从处理程序返回任何内容:
现在,finalResult最终将成为值@ "OK"或NSError .
您可以将最终结果保存到数组中:
所有任务成功完成后继续:
设置promise的值将被称为:“resolving” . 您只能通过ONCE解决承诺 .
您可以将带有完成处理程序或完成委托的任何异步方法包装到返回promise的方法中:
完成任务后,可以“履行”承诺,将结果值传递给它,或者可以“拒绝”传递原因(错误) .
根据实际的实施,也可以取消Promise . 比如说,您持有对请求操作的引用:
您可以取消异步任务如下:
要取消关联的异步任务,请在包装器中注册失败处理程序:
注意:您可以注册成功或失败处理程序,何时,何地以及根据需要注册 .
所以,你可以通过承诺做很多事 - 甚至比这个简短的介绍更多 . 如果你读到这里,你可能会想到如何解决你的实际问题 . 它就在那里 - 它是几行代码 .
我承认,这个对promises的简短介绍非常粗糙,对Objective-C开发人员来说也是一个新手,并且可能听起来不常见 .
你可以在JS社区中阅读很多关于promises的内容 . Objective-C中有一个或三个实现 . 实际实现不会超过几百行代码 . 它发生了,我是其中一个的作者:
RXPromise .
拿一粒盐,我可能完全有偏见,显然所有其他人都曾经处理过Promise . ;)