细说 NSOperation

本文详细介绍了在实现同步、异步 NSOperation 时分别须要实现哪些方法、注意哪些问题。最后对 GCD 与 NSOperation Queue 做了一个简单的对比。html

本文同时发表于个人我的博客ios

Overview


在 iOS 中实现并发编程主要有三种方式:GCD、NSOperation Queue以及Thread,其中前二者使用普遍。 在正式开始以前有必要区分两组概念:同步、异步与串行、并行。git

  • 同步(Synchronous)、异步(Asynchronous)一般指方法(或函数),同步方法表示直到任务完成才返回(如:dispatch_sync),异步方法则是将任务抛出去,在任务没完成前就返回(如:dispatch_async);
  • 串行(Serial)、并行(Concurrent)一般指 App 执行一组任务的模式,串行表示一次只能执行一个任务,只有当前一个任务完成后才启动下一个任务,而并行指能够同时执行多个任务。最多见的莫过于 GCD 中的串行、并行队列。

NSOperation Queue + NSOperation 做为 iOS 中『高级的、面向对象的并发编程方式』耳熟能详,但具体到一些细节问题上认识每每又比较模糊。本文在苹果官方文档 Concurrency Programming GuideNSOperation Class Reference 以及 NSOperationQueue Class Reference 的基础上作了一次疏理和总结。github

NSOperation


NSOperation 自己是个抽象类,在使用前必须子类化(系统预约义了两个子类:NSInvocationOperationNSBlockOperation)。那问题来了,在子类化过程当中,须要重写父类的哪些方法?编程

这首先就要了解一下NSOperation类中几个重要方法的默认实现: 安全

在 NSOperation 中还有一个重要概念:operation 的状态,而且当状态变化时须要经过 KVO 的方式通知外:
回到前面那个问题:子类化 NSOperation 时须要重写哪些方法? 这取决于子类化后的 operation 是 Synchronous 仍是 Asynchronous(NSOperation 默认是Synchronous)。

Synchronous VS. Asynchronous Operations


因为操做 NSOperation 与 NSOperation 任务的执行每每在不一样的线程上进行,在继续以前须要强调线程安全问题:『NSOperation 自己是 thread-safe,当咱们在子类重写或自定义方法时一样须要保证 thread-safe』。网络

Synchronous Operations

对于 Synchronous Operation,在调用其 start 方法的线程上同步执行该 operation 的任务,start 方法返回时 operation 执行完成。所以,对于 Synchronous Operation 通常只需重写 main 方法便可(start方法的默认实现已实现相关 KVO 功能)。并发

Asynchronous Operations

然而对于 Asynchronous Operation,调用其 start 方法后,在 start 返回时 operation 的任务可能还没完成(为了实现异步,通常须要在其余线程执行 operation 的具体任务)。所以 start 方法默认实现不能知足异步须要(默认实现会在start返回前将 isExecuting 置为 NO、isFinished 置为 YES,并产生 KVO 通知)。此时至少须要重写如下方法:app

  • start: 咱们知道 NSOperation 自己不具有并发(或者说异步执行)能力,所以须要 start 方法来实现,能够经过建立子线程或其余异步方式完成。同时须要在任务开始前将 isExecuting 置为YES 并抛出 KVO 通知。 『重写的 start 方法必定不能调用 [super start]
  • asynchronous 返回 YES,通常不须要抛出 KVO 通知
  • executing 返回 operation 的执行状态,在其值发生变化时须要在 isExecuting 上抛出 KVO 通知
  • finished 返回 operation 的完成状态,一样值变化时须要在 isFinished 上抛出 KVO 通知

这里咱们看看著名的网络框架 AFNetworking 中关于 NSOperation 的使用:框架

AFNetworking 3.0 全面使用 NSURLSession,而 NSURLSession 自己是异步的、且没有 NSURLConnection 须要 runloop 配合的问题,所以在3.0版本中并无使用 NSOperation,代码获得很大的简化。这里咱们说的是 AFNetworking 2.3.1 版本。

在 AFNetworking 中 AFURLConnectionOperation 是个异步的 NSOperation 子类,其 start 方法以下:

从上面 start 方法的实现能够看到:

  1. 用 lock(递归锁) 保证了thread-safe;
  2. 检查了 operation 是否已被 cancel;
  3. 检查了 operation 是否已 ready;
  4. 经过子线程实现并发;
  5. 在 state setter 中实现了 KVO。
    再来看看 AFURLConnectionOperation 使用的子线程:
    能够看到,全部 AFURLConnectionOperation 实例底层使用的是同一个子线程,并在该线程中启动了 runloop(NSURLConnection 的网络回调必需要有 runloop 的配合,经过port-based input source 唤醒 runloop 处理网络事件),也就是说 AFURLConnectionOperation 是在一条常驻子线程中处理网络回调。

前面咱们提到 operation 被 cancel 时也被认为是完成,这点在自定义 start 时一样须要注意:

在 AFURLConnectionOperation 的 cancelConnection 以及 connection:didFailWithError: 方法中都会调用其 finish 方法:
ps:虽然 NSOperation 支持 cancel,但在调用 cancel 方法后该如何处理彻底由咱们自定义的 start 方法决定(固然良好的设计应该要符合 cancel 的语义)。

同时,AFURLConnectionOperation 也实现了如下方法:

关于 NSOperation 其余细节问题


  • dependencies: 咱们能够在 operation 间添加依赖关系,在某个 operation 所依赖的 operations 完成以前,其一直处于未就绪状态(isReady 为 NO)。 须要注意的是,依赖关系是 operation 自身的状态,也就是说有依赖关系的 operations 能够处在不一样的 NSOperationQueue 中。

  • isReady: isReady 默认实现主要处理 operation 间的依赖关系,当咱们自定义该方法时须要考虑 super 的值,如 AFURLConnectionOperation中关于 isReady 的实现:

  • qualityOfService: 用于表示 operation 在获取系统资源时的优先级,默认值:NSQualityOfServiceBackground,咱们能够根据须要给 operation 赋不一样的优化级,如最高优化级:NSQualityOfServiceUserInteractive

  • queuePriority: 用于设置 operation 在 operation queue 中的相对优化级,同一 queue 中优化级高的 operation(isReady 为 YES) 会被优先执行。须要注意区分qualityOfService(在系统层面,operation 与其余线程获取资源的优先级)与queuePriority(同一 queue 中 operation 间执行的优化级)的区别。 同时,须要注意dependencies(严格控制执行顺序)与queuePriority(queue 内部相对优先级)的区别。

NSOperation Queue


NSOperation Queue 用于管理、执行 NSOperation,不管其中的 operation 是并行仍是串行,queue 都会在子线程(借用 GCD)中执行 operation。 从上小节咱们知道,实现异步 operation 比同步 operation 要复杂许多,所以若是打算将 operation 加入 queue 中,则彻底能够将 operation 实现为同步方式。 对于 queue 中已就绪的 operation,queue 会选择 queuePriority 值最大的 operation 执行。

关于 NSOperation Queue 有两点须要强调:

  • cancelAllOperations:用于取消队列中的 operations,对 queue 中全部 operations 调用 cancel方法。(从上小节咱们知道,对 operation 调用 cancel 方法后的效果彻底由 operation 本身决定。cancel 惟一能影响的就是清除 operation 的依赖关系,使其当即能够被执行)。此时 queue 并不会 remove 其中的 operations,remove 操做仅发生在 operation 完成时。
  • suspended:将该属性置为 YES,会阻止 queue 执行新的 operation,但已经在执行中的 operation 不受此影响。

GCD vs. NSOperation Queue


GCD 与 NSOperation Queue 做为常见的并发编程方式,在使用时该如何选择? 首先,对比一下咱们关心的几个问题:

咱们能够看到,NSOperation Queue 做为高级 API,有不少 GCD 没有的功能,如须要支持:控制并发数、取消、添加依赖关系等须要使用 NSOperation Queue。 另外,因为 block 可复用性没有 NSOperation 好,对于独立性强、可复用性高的任务建议使用 NSOperation 实现。 固然,NSOperation 在使用时须要 sub-classing,工做量较大,对于简单的任务使用 GCD 便可。

别忘了,咱们还有第三种选择:NSThread。因为使用 NSThread 时须要处理线程相关的问题,通常不多使用。但不管是 GCD 仍是 NSOperation Queue,其中的任务具体什么时候执行是由系统控制的,对于实时性要求很高的任务则可使用 NSThread。

小结


本文简单讨论了在使用 NSOperation 时须要重写哪些方法、注意哪些问题。同时也对 GCD 与 NSOperation Queue 做了简单对比,在清楚了它们各自的特色以后再作选择时会更加清晰。

参考资料

Concurrency Programming Guide

NSOperation Class Reference

NSOperationQueue Class Reference

相关文章
相关标签/搜索