第43条:掌握GCD及操作队列的使用时机

  本条要点:(作者总结)

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

  GCD 技术确实很棒,不过有时侯采用标准系统库的组件,效果会更好。一定要了解每项技巧的使用时机,如果选错了工具,那么编出来的代码就会难于维护。

  很少有其他技术能与GCD 的同步机制相媲美。对于那些只需要执行一次的代码来说,也是如此,使用 GCD 的 dispatch_once 最为方便。然而,在执行后台任务时,GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue,它虽然与 GCD 不同,但是却与之相关,开发者可以把操作以 NSOperation 子类的形式放在队列中,而这些操作也能够并发执行。其与 GCD 派发队列有相似之处,这并非巧合。“操作队列”(operation Queue)在 GCD 之前就有了,其中某些设计原理因操作队列而流行,GCD 就是基于这些原理构建的。实际上,从 iOS 4 与 Mac OSX 10.6 开始,操作队列在底层是用 GCD 来实现的。

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

  用 NSOperationQueue 类的 “addOperationWithBlock:” 方法搭配 NSBlockOperation 类来使用操作队列,其语法与纯 GCD 方式非常类似。使用 NSOperation 及 NSOperationQueue 的好处如下:

  •  取消某个操作。如果使用操作队列,那么想要取消操作是很容易的。运行任务之前,可以在 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 类可以在代码中多次使用,它们符合软件开发中的 “不重复”(Don't Repeat Yourself, DRY)原则。

  正如大家所见,操作队列有很多地方胜过派发队列。操作队列提供了多种执行任务的方式,而且都是写好了的,直接就能使用。开发者不用再编写复杂的调度器,也不用自己来实现取消操作或指定操作优先级的功能,这些事情操作队列都已经实现好了。

  有一个 API 选用了操作队列而非派发队列,这就是 NSNotificationCenter ,开发者可通过其中的方法来注册监听器,以便在发生相关事件时得到通知,而这个方法接受的参数是块,不是选择子。方法原型如下:

1   - (id)addObserverForName:(NSString *)name object:(id)object queue:(NSOperationQueue *)queue usingBlock:(void(^)(NSNotification *))block;

  本来这个方法也可以不使用操作队列,而是把处理通知事件所用的块安排在派发队列里。但实际上并没有这么做,其设计者显然使用了高层的 Objective-C API。在这种情况下,两套方案的运行效率没多大差距。设计这个方法的人可能不想使用派发队列,因为那样将依赖于 GCD,而这种依赖没有必要,前面说过,块本身和 GCD 无关,所以如果仅使用块的话,就不会引入对 GCD 的依赖了。也有可能编写这个方法的人想全部用 Objective-C 来描述,而不想使用纯 C 的东西。

  经常会有人说:应该尽可能选用高层 API,只在确有必要时才求助于底层。笔者也同意这个说法,但我并不盲从。某些功能确实可以用高层的 Objective-C 方法来做,但这并不等于说它就一定比底层实现方案好。要想确定哪种方案更佳,最好还是要测试一下性能。

  END

posted @ 2017-08-21 22:47  鳄鱼不怕牙医不怕  阅读(221)  评论(0编辑  收藏  举报