NSOperation以及NSOperationQueue的使用
本文于2015.11.22进行了修改。
1. 什么NSOperation
NSOperation为控制任务状态、优先级、依赖关系以及任务管理提供了一种线程安全的结构。可以通过调用start
方法来手动启动一个任务,或者把它加入到NSOperationQueue中,当它到达队列头部时自动启动。
2. NSOperation的三个执行状态
isReady
、isExecuting
、isFinished
是NSOperation生命周期中的三个互斥的状态。官方文档中关于NSOperation状态维护的说明非常详细:
3. Synchronous和Asynchronous类型的NSOperation
- synchronous vs asynchronous的区别:
In a synchronous operation, the operation object does not create a separate thread on which to run its task.
An asynchronous operation object is responsible for scheduling its task on a separate thread. The operation could do that by starting a new thread directly, by calling an asynchronous method, or by submitting a block to a dispatch queue for execution. - synchronous vs asynchronous的使用选择:
If you always plan to use queues to execute your operations, it is simpler to define them as synchronous.
When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread. Therefore, if you always run operations by adding them to an operation queue, there is no reason to make them asynchronous.
If you execute operations manually, though, you might want to define your operation objects as asynchronous.
Defining an asynchronous operation requires more work, because you have to monitor the ongoing state of your task and report changes in that state using KVO notifications. But defining asynchronous operations is useful in cases where you want to ensure that a manually executed operation does not block the calling thread.
4. NSOperation和NSOperationQueue的系统实现
在介绍后面的小节之前,为了便于说吗,我们先约定两个词:
终止任务=设置executing
和finished
属性的值,
clean up=停止实际的图片下载、数据处理等操作。
NSOperation中一些方法的系统实现:
start
方法:首先检查cancelled
属性和finished
属性(我认为没有必要检查isFinished属性),如果有一个为YES的话,立即终止任务;如果都为NO, 则修改executing属性的值,调用main方法cancel
方法: 如果NSOperation不在队列里的话, 则立即终止任务;如果在队列里的话,则仅设置cancelled
和ready
属性的值,等待queue调用它的start
方法时终止任务(如果cancel
时尚未start
),或者当它的其他方法执行中检查cancelled
变量时终止任务(如果cancel
时已经start
)main
方法:什么也不做
NSOperationQueue中一些方法的系统实现:
cancelAllOperations
: 对queue中的每个operation调用cancel
方法
5. Subclassing NSOperation
The NSOperation class provides the basic logic to track the execution state of your operation but otherwise must be subclassed to do any real work.
-
子类化Synchronous operation
For non-concurrent operations, you typically override only one method: main. Into this method, you place the code needed to perform the given task.
一个例子是:http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues -
子类化Asynchronous operation
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:start
: responsible for starting the operation in an asynchronous manner; should also update the execution state of the operation as reported by the executing property; should also check to see if the operation itself was cancelled before actually starting the task.
At no time in your start method should you ever call super. When you define a concurrent operation, you take it upon yourself to provide the same behavior that the default start method provides.asynchronous
: YESexecuting
: Your executing property must also provide the status in a thread-safe manner.finished
: Your finished property must also provide the status in a thread-safe manner.
Upon completion or cancellation of its task, your concurrent operation object must generate KVO notifications for both the isExecuting and isFinished key paths to mark the final change of state for your operation.In addition to generating KVO notifications, your overrides of the executing and finishedproperties should also continue to report accurate values based on the state of your operation.
6. 响应取消命令
关于如何响应取消NSOperation的操作,苹果官方文档上是这么建议的: 在NSOperation子类的所有方法中,任何耗时的操作之后都检查cancelled
属性的值。如果为YES的话,终止任务,并设置executing
和finished
属性的值。 不需要重写cancel方法。这个例子也是这么处理的。
但是在实际操作中,按照上述做法,当用户调用了cancel
方法取消某个操作时并不能实时停止任务。因此,通常的做法是:
start
方法:首先检查cancelled
属性。如果为YES的话,立即终止任务;否则修改executing
属性的值,开启任务cancel
方法:立即clean up, 并设置cancelled
为YES,(如果你自己管理ready
属性,那么也设置ready
为YES),等待queue调用它的start
方法时终止任务(如果cancel
时尚未start
),或者直接终止任务(如果cance
l时已经start
)
可以看出,上面两个方法的实现基本是对系统实现的重复,只不过增加了与用户任务相关的clean up操作。
需要注意的是,对于一个在NSOperationQueue中的NSOperation,如果它尚未start
, 那么不允许把它的finished
属性设置成YES。 也就是说,必须依次经过了ready
、executing
这两个状态之后,才能变为finished
状态
5. ASI的一个bug
最近在跟进项目中遇到的ASI(版本为:v1.8.1-61 2011-09-19)在弱网情况下crash的问题, 发现在cancel满足以下三个条件的ASIHTTPRequest时必现crash:
- 在queue中
- 尚未start
- cache中有关于该请求的有效缓存,且该请求的策略允许从缓存中读取
原因就在于在这种路径上直接将ASIHTTPRequest的isFinished
设置成为了YES, 违背了本文第2节中提到的第二点注意事项。
修改之后,一个ASIHTTPRequest的状态转换如下:
注意:在ASI早期的版本中,有过类似的, cancel掉queue里一个尚未start的请求时crash的bug. 可以参考http://stackoverflow.com/questions/3291834/asihttprequest-dealloc-and-exc-bad-access-problem
Refs
http://nshipster.com/nsoperation/
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/#//apple_ref/doc/uid/TP40004591-RH2-SW18
http://stackoverflow.com/questions/3291834/asihttprequest-dealloc-and-exc-bad-access-problem
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues