NSOperation
几乎每个开发者都知道,让 App 快速响应的秘诀是把耗时的计算丢到后台线异步去做。于是,Modern Objective-C 开发者有两个选择:GCD 和 NSOperation.
由于 GCD 已经发展的比较主流了,我们稍后再说它,先说说面向对象的 NSOperation.
NSOperation 表示一个单独的计算单元,它是一个抽象类(很类似 Java 里的 Runnable 接口),给子类提供了一些非常有用且线程安全的特性,比如状态 (state
), 优先级 (priority
), 依赖 (dependencies
) 以及取消 (cancellation
). 如果你不想子类化 NSOperation,可以选择使用 NSBlockOperation 这个 NSOperation 的子类,它可以把一个 block 包装成为一个 NSOperation.
非常适合使用 NSOperation 的任务例子包括 network requests, 图片的缩放,语言处理或者其他一些重复的、结构化的以及需要运行较长时间来处理数据的任务。
但是,仅仅把计算包装成一个对象,没有一些监管也不会非常的有用,这时 NSOperationQueue
就出现了。
NSOperationQueue 控制各个 operation 的并发执行。它像是一个优先级队列,operation 大致的会按 FIFO 的方式被执行,不过带有高优先级的会跳到低优先级前面被执行(用 NSOperation 的 queuePriority 方法来设置优先级)。 NSOperationQueue 支持并发的执行 operations,通过 maxConcurrentOperationCount 来指定最大并发数,就是同时有最多有多少个 operation 同时被运行。
可以通过调用 - start 方法来启动一个 NSOperation,或者把它放到 NSOperationQueue 里,当到达队列最前端时也会被自动的执行。
现在来看看 NSOperation 的几个不同的特性,以及如何如果使用和子类化它:
状态 State
NSOperation 构建了一个非常优雅的状态机来描述一个 operation 的执行过程:
isReady -> isExecuting -> isFinished
State 是通过这些 keypath 的 KVO 通知来隐式的得到,而不是显式的通过一个 state 的属性。就是说,当一个 operation 已经准备就绪,将要被执行时,它会为 isReady
keyPath 发送一个 KVO 的通知,对应的属性值也会变为 YES.
为了构造一致的状态,下面每个属性都与其他属性相互排斥:
- isReady: 如果 operation 已经做好了执行的准备返回 YES,如果它所依赖的操作存在一些未完成的初始化步骤则返回 NO。
- isExecuting: 如果 operation 正在执行它的任务返回 YES,否则返回 NO。
- isFinished: 任务成功的完成了执行,或者中途被 Cancel,返回 YES。NSOperationQueue 只会把 isFinished 为 YES 的 operation 踢出队列,isFinished 为 NO 的永远不会被移除,所以实现时一定要保证其正确性,避免死锁的情况发生。
取消 Cancellation
如果正在进行的 operation 所做的工作不再有意义,尽早的取消掉是非常有必要的。取消一个 operation 可以是显式的调用 cancel 方法,也可以是 operation 依赖的其他 operation 执行失败。
和 state 类似,当 NSOperation 的被取消,是通过 isCancelled
keypath 的 KVO 来获得。当 NSOperation 的子类覆写 cancel 方法时,注意清理掉内部分配的资源。特别注意的是,这时 isCancelled 和 isFinished 的值都变为了 YES,isExecuting 为值变为 NO。
一个需要格外注意的地方是和单词 “cancel” 有关的两个词:
- cancel : 带一个”l” 表示方法 (动词)
- isCancelled : 带两个”l” 表示属性(形容词)
优先级 Priority
所有的 operation 在 NSOperationQueue 中未必都是一样的重要,设置 queuePriority
属性就可以提升和降低 operation 的优先级,queuePriority
属性可选的值如下:
- NSOperationQueuePriorityVeryHigh
- NSOperationQueuePriorityHigh
- NSOperationQueuePriorityNormal
- NSOperationQueuePriorityLow
- NSOperationQueuePriorityVeryLow
另外,operation 可以指定一个 threadPriority
值,它的取值范围是 0.0 到 1.0,1.0 代表最高的优先级。queuePriority
决定执行顺序的优先级,threadPriority
决定当 operation 开始执行之后分配的计算资源的多少。
依赖 Dependencies
取决于你的 App 的复杂性,可能会需要把一个大的任务分成多个子任务,这时 NSOperation 依赖就排上用场了。
比如从服务器上下载和缩放图片的过程,你可能会想把下载图片作为一个 operation,缩放作为另外一个(这样也可以复用下载图片和缩放图片的代码)。然后,一个图片在从服务器上下载下来之前是没有办法缩放的,于是我们说缩放图片的 operation 依赖从服务器上下载图片的 operation,后者必须先完成,前者才能开始执行。用代码表示是这样的:
1 | [resizingOperation addDependency:networkingOperation]; |
一个 operation 只有在它依赖的所有的 operation 的 isFinished 都为 YES 的时候才会开始执行。要记住添加到 queue 里的所有的 operation 的依赖关系,并避免循环依赖,比如 A 依赖 B,B 依赖 A,这样会产生死锁。
completionBlock
completionBlock
是在 iOS4 和 Snow Leopard 中添加的一个非常有用的特性。当一个 NSOperation 完成之后,就会精确地只执行一次 completionBlock
。我们需要在 operation 完成之后想做点什么的时候这个属性就会非常有用。比如当一个网络请求结束之后,可以在 completionBlock
里处理返回的数据。
总结
NSOperation 依然是 Modern Objective-C 程序员杀手锏里的重要工具。相对于 GCD 非常适用于 in-line 的异步处理,NSOperation 提供了更综合的、面向对象的计算模型,非常适用于封装结构化的数据,重复性的任务。把它加到你的下个项目中,给你的用户和你自己都带来乐趣吧!
译者注
本文编译自 NSHipster 里的 NSOperation 一文,感谢作者 Mattt Thompson, 来头很大,这是他的简介:
Mattt Thompson is the Mobile Lead at Heroku, and the creator & maintainer of AFNetworking and other popular open-source projects, including Postgres.app & Induction. He also writes about obscure & overlooked parts of Cocoa on NSHipster.
最上面的图片是来自于 WWDC2013 中的 “Hidden Gems in Cocoa and Cocoa Touch”(228)中 Mattt 讲 NSOperation 时的截图,这个视频一共有 30 个 tips,这是第 8 个 tip,大部分的内容我是第一次知道,非常值得看,而且如果有条件的话,建议下载 HD 版本的视频来看,效果比 SD 好太多。字幕文件在我的这个 repo 里,:)
如有文中有不准确的地方,欢迎留言指正 :)
Enjoy!