NSOperation

在 OSX 10.6 和 iOS 4 之后,NSOperation 与 NSOperationQueue 基于 GCD 进行了重写,也就是 GCD 的 Cocoa 对象版本。

NSOperation

NSOpeation 是一个抽像类,不能直接实例化,它提供了一些非常有用且线程安全的特性:状态(state),优先级(priority),依赖(dependencies)以及取消(cancellation)。

它提供了一个简化的子类 NSBlockOperation,可以直接使用。也可以通过继承 NSOperation 来使用:

- NSBlockOperation

A class you use as-is to execute one or more block objects concurrently. Because it can execute more than one block, a block operation object operates using a group semantic; only when all of the associated blocks have finished executing is the operation itself considered finished.

NSBlockOperation 可添加多个 Block,创建后仍可追加。

NSInvocationOperation 在 Swift 中被移除

1
2
3
4
5
6
7
8
9
10
11
12
let op: NSBlockOperation = NSBlockOperation (block: {
println("I'm the O1")
})
// 简写
let op = NSBlockOperation(){
println("I'm the O1")
}
// 执行
op.main()
// op.start()

- 自定义 NSOperation 对象

The base class for defining custom operation objects. Subclassing NSOperation gives you complete control over the implementation of your own operations, including the ability to alter the default way in which your operation executes and reports its status.

- 串行队列

定义非并发 operation 要简单许多,只需要执行主任务,并正确地响应取消事件,NSOperation 处理了其它所有事情。

For non-concurrent operations, you typically override only one method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CustomOperation: NSOperation {
var isExecuting = false;
var isFinished = false;
override func main() {
if (self.cancelled) {
return
} else {
println("custom operation work is done here.")
for (var i = 0; i < 5; i ++) {
println("i \(i)")
sleep(1)
}
}
self.willChangeValueForKey("executing")
isExecuting = false
self.didChangeValueForKey("executing")
self.willChangeValueForKey("finished")
isFinished = true
self.didChangeValueForKey("finished")
if (finished) {
println("completed")
} else {
println("Not completed")
}
}
}

- 并行队列

If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:

  • start (必须)所有并发操作都必须覆盖这个方法,以自定义的实现替换 默认行为。手动执行一个操作时,你会调用 start 方法。因此你对这 个方法的实现是操作的起点,设置一个线程或其它执行环境,来执 行你的任务。你的实现在任何时候都绝对不能调用 super。
  • main (可选)这个方法通常用来实现 operation 对象相关联的任务。尽管 你可以在 start 方法中执行任务,使用 main 来实现任务可以让你的 代码更加清晰地分离设置和任务代码
  • isExecuting isFinished
    (必须)并发操作负责设置自己的执行环境,并向外部 client 报告 执行环境的状态。因此并发操作必须维护某些状态信息,以知道是 否正在执行任务,是否已经完成任务。使用这两个方法报告自己的 状态。 这两个方法的实现必须能够在其它多个线程中同时调用。另外这些 方法报告的状态变化时,还需要为相应的 key path 产生适当的 KVO 通知。
  • isConcurrent (必须)标识一个操作是否并发 operation,覆盖这个方法并返回 YES

    

状态 State

NSOperation 构建了一个非常优雅的状态机来描述一个 operation 的执行过程:

isReady -> isExecuting -> isFinished

  • isReady
  • isExecuting
  • isFinished
  • isCancelled

取消 Cancel

operation 开始执行之后,会一直执行任务直到完成,或者显式地取 消操作。取消可能在任何时候发生,甚至在 operation 执行之前。尽 管 NSOperation 提供了一个方法,让应用取消一个操作,但是识别出取 消事件则是你的事情。如果 operation 直接终止,可能无法回收所有已 分配的内存或资源。因此 operation 对象需要检测取消事件,并优雅地 退出执行。

operation 对象定期地调用 isCancelled 方法,如果返回 YES(表示已 取消),则立即退出执行。不管是自定义NSOperation 子类,还是使用 系统提供的两个具体子类,都需要支持取消。isCancelled 方法本身非常 轻量,可以频繁地调用而不产生大的性能损失。以下地方可能需要调用 isCancelled:

  • 在执行任何实际的工作之前
  • 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用
    多次
  • 代码中相对比较容易中止操作的任何地方

如果正在进行的 operation 所做的工作不再有意义,尽早的取消掉是非常有必要的。取消一个operation可以是显式的调用cancel方法,也可以是operation依赖的其他operation执行失败。

和state类似,当NSOperation的被取消,是通过isCancelledkeypath的KVO来获得。当NSOperation的子类覆写cancel方法时,注意清理掉内部分配的资源。特别注意的是,这时isCancelled和isFinished的值都变为了YES,isExecuting为值变为NO。

一个需要格外注意的地方是和单词“cancel”有关的两个词:

  • cancel: 表示方法
  • isCancelled: 表示属性

completionBlock

当一个 NSOperation 执行完成之后,就会精确地只执行一次 completionBlock。我们需要在operation完成之后想做点什么的时候这个属性就会非常有用。比如当一个网络请求结束之后,可以在 completionBlock 里处理返回的数据。

NSOperationQueue

NSOperationQueue 控制各个 operation 的并发执行。它像是一个优先级队列,operation 大致的会按 FIFO 的方式被执行,不过带有高优先级的会跳到低优先级前面被执行(用 NSOperation 的 queuePriority 方法来设置优先级)。NSOperationQueue 支持并发的执行 operations,通过 maxConcurrentOperationCount 来指定最大并发数,就是同时有最多有多少个 operation 同时被运行。

只有 NSOperation 才能加入到 NSOperationQueue。operation 被加入到 Queue 中会被自动执行,但执行时间不确定,可以通过 调用 start来启动。

Dependencies

如果 operation 已经在一个 queue 中, queue 就可以在任何时候执行这个 operation。如果你需要手动执行该 operation,就自己调用 operation 的 start 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func blockOperationsTest1(){
var operationQueue = NSOperationQueue()
var op1 = NSBlockOperation(){
println("op1")
}
var op2 = NSBlockOperation() {
println("op2")
}
op2.addDependency(op1)
// 这里先添加哪一个都没区别
operationQueue.addOperation(op2)
operationQueue.addOperation(op1)
}

Priority

1
2
3
4
5
6
7
enum NSOperationQueuePriority : Int {
case VeryLow
case Low
case Normal
case High
case VeryHigh
}

waitUntilFinished

有时,需要一个 Queue 完全执行结束后,才能继续下面的任务。不然任务会同步执行。

如何构造一个 Serial Queue?

虽然 NSOperationQueue 类设计用于并发执行 Operations,你也可以 强制单个 queue 一次只能执行一个 Operation。 setMaxConcurrentOperationCount: 方法可以配置 operation queue 的最 大并发操作数量。设为 1 就表示 queue 每次只能执行一个操作。不过 operation 执行的顺序仍然依赖于其它因素,像操作是否准备好和优先级 等。因此串行化的 operation queue 并不等同于 GCD 中的串行 dispatch queue。

MainQueue

和GCD中介绍的一样,所有 UI 操作都必须回到主线程:

1
NSOperationQueue.mainQueue()

REF::