NSOperation

Xueshi Lv4

几乎每个开发者都知道,让 App 快速响应的秘诀是把耗时的计算丢到后台线异步去做。于是,Modern Objective-C 开发者有两个选择:GCDNSOperation.

由于 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
2
3
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];

一个 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!

  • Title: NSOperation
  • Author: Xueshi
  • Created at : 2013-07-28 00:00:00
  • Updated at : 2026-06-07 01:44:22
  • Link: http://xueshi.me/2013/07/28/nsoperation/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments