第四十三条 掌握GCD和操作队列(NSOperation)的使用时机

2017/9/16 posted in  Effective OC2.0

虽然GCD很棒,但是我们有时候也应该选用正确的工具来使用。所以我们这里来介绍下NSOperation

很少有其他技术能与GCD的同步机制相媲美。对于那些只需执行一次的代码来说,使用GCDdispatch_once最为方便。

但是如果执行后台任务,NSOperation也许更适合。NSOperation是在GCD出现之前就有的一种技术。甚至现在操作队列在底层是用GCD来实现的。

在两者的诸多差别中,首先要注意:GCD是纯C的API,而操作队列则是Objective-C的对象。GCD中,任务用块来表示,而块是个轻量级数据结构(参见第37条)。与之相 反,“操作"(operation)则是个更为重量级的Objective-C对象。虽说如此,但GCD并不总是最佳方案。有时候采用对象所带来的开销微乎其微,使用完整对象所带来的好处反而大大超过其缺点。

所以我们来介绍下它的好处:

  • 取消某个操作。如果使用操作队列,那么想要取消操作是很容易的。运行任务之前, 可以在NSOperation对象上调用cancel方法,该方法会设置对象内的标志位,用以表明此任务不需执行,不过,已经启动的任务无法取消。若是不使用操作队列,而是把块安排到GCD队列,那就无法取消了。那套架构是“安排好任务之后就不管了"(fire and forget)。

  • 指定操作间的依赖关系。一个操作可以依赖其他多个操作。开发者能够指定操作之间的依赖体系,使特定的操作必须在另外一个操作顺利执行完毕后方可执行。比方说,从服务器端下载并处理文件的动作,可以用操作来表示,而在处理其他文件之前,必须先下载“清单文件”(manifest file)。后续的下载操作,都要依赖于先下载清单文件这一操作。如果操作队列允许并发的话,那么后续的多个下载操作就可以同时执行,但前提是它们所依赖的那个清单文件下载操作已经执行完毕。

  • 通过键值观测机制监控NSOperation对象的属性。NSOperation对象有许多属性都适合通过键值观测机制(简称KVO)来监听,比如可以通过isCancelled属性来判断任务是否已取消,又比如可以通过isFinished属性来判断任务是否已完成。如果想在某个任务变更其状态时得到通知,或是想用比GCD更为精细的方式来控制所要执行的 任务,那么键值观测机制会很有用。

  • 指定操作的优先级。操作的优先级表示此操作与队列中其他操作之间的优先关系。优先级高的操作先执行,优先级低的后执行。操作队列的调度算法(scheduling algorithm)“不透明"(opaque),但必然是经过一番深思熟虑才写成的。反之,GCD则没有直接实现此功能的办法。GCD的队列确实有优先级,不过那是针对整个队列来说的,而不是针对每个块来说的。而令开发者在GCD之上自己来编写调度算法,又不太合适。因此,在优先级这一点上,操作队列所提供的功能要比GCD更为便利。

NSOperation对象也有“线程优先级”(thread priority),这决定了运行此操作的线程处在 何种优先级上。用GCD也可以实现此功能,然而采用操作队列更简单,只需设置一个属性。

  • 重用 NSOperation对象。系统内置了一些NSOperation的子类(比如NSBlockOperation)供开发者调用,要是不想用这些固有子类的话,那就得自己来创建了。这些类就是普通的Objective-C对象,能够存放任何信息。对象在执行时可以充分利用存于其中的信息,而且还可以随意调用定义在类中的方法。这就比派发队列中那些简单的块要强大许多。这些NSOperation类可以在代码中多次使用,它们符合软件开发中的“不重复”原则。

正如大家所见,操作队列有很多地方胜过派发队列。操作队列提供了多种执行任务的方式,而且都是写好了的,直接就能使用。

所以我们说,某些功能确实可以用高层的Objective-C方法来做,但这并不等于说它就一定比底层实现方案好。要想确定哪种方案更佳,最好还是测试一下性能。

要点

  • 在解决多线程与任务管理问题时,派发队列并非唯一方案。
  • 操作队列提供了一套高层的Objective-C API,能实现纯GCD所具备的绝大部分功能,而且还能完成一些更为复杂的操作,那些操作若改用GCD来实现,则需另外编写代码。