#主要内容
经过上一章的学习,我们已经理解了多线程编程的基本概念,以及GCD的简单使用。在这一章中将会介绍和NSOperation
和和NSOperationQueue
。主要涉及这几个方面:
NSOperation
和NSOperationQueue
用法介绍NSOperation
的暂停、恢复和取消- 通过KVO对
NSOperation
的状态进行检测 - 多个
NSOperation
的之间的依赖关系
#NSOperation
从简单意义上来说,NSOperation
是对GCD中的block进行的封装,它也表示一个要被执行的任务。
与GCD中的block类似,NSOperation
对象有一个start()
方法表示开始执行这个任务。
不仅如此,NSOperation
表示的任务还可以被取消。它还有三种状态isExecuted
、isFinished
和isCancelled
以方便我们通过KVC对它的状态进行监听。
想要开始执行一个任务可以这么写:
let operation = NSBlockOperation { () -> Void in
print(NSThread.currentThread())
}
operation.addExecutionBlock { () -> Void in
print("execution block1 -- \(NSThread.currentThread())")
}
operation.start()
以上代码会得到这样的执行结果:
<NSThread: 0x7f89b1c070f0>{number = 1, name = main}
execution block1 -- <NSThread: 0x7f89b1e17030>{number = 2, name = (null)}
首先我们创建了一个NSBlockOperation
,并且设置好它的block,也就是将要执行的任务。这个任务会在主线程中执行。
用NSBlockOperation
是因为NSOperation
是一个基类,不应该直接生成NSOperation
对象,而是应该用它的子类。NSBlockOperation
是苹果预定义的子类,它可以用来封装一个或多个block,后面会介绍如何自己创建NSOperation
的子类。
同时,还可以调用addExecutionBlock
方法追加几个任务,这些任务会并行执行(也就是说很有可能运行在别的线程里)。
最后,调用start
方法让NSOperation
方法运行起来。start
是一个同步方法。
#NSOperationQueue
刚刚我们知道,默认的NSOperation
是同步执行的。简单的看一下NSOperation
类的定义会发现它有一个只读属性asynchronous
这意味着如果想要异步执行,就需要自定义NSOperation
的子类。或者使用NSOperationQueue
NSOperationQueue
类似于GCD中的队列。我们知道GCD中的队列有三种:主队列、串行队列和并行队列。NSOperationQueue
更简单,只有两种:主队列和非主队列。
我们自己生成的NSOperationQueue
对象都是非主队列,主队列可以用NSOperationQueue.mainQueue
取得。
NSOperationQueue
的主队列是串行队列,而且其中所有NSOperation
都会在主线程中执行。
对于非主队列来说,一旦一个NSOperation
被放入其中,那这个NSOperation
一定是并发执行的。因为NSOperationQueue
会为每一个NSOperation
创建线程并调用它的start
方法。
NSOperationQueue
有一个属性叫maxConcurrentOperationCount
,它表示最多支持多少个NSOperation
并发执行。如果maxConcurrentOperationCount
被设为1,就以为这个队列是串行队列。
因此,NSOperationQueue
和GCD中的队列有这样的对应关系:
NSOperationQueue | GCD | |
---|---|---|
主队列 | NSOperationQueue.mainQueue | dispatch_get_main_queue() |
串行队列 | 自建队列maxConcurrentOperationCount为1 | dispatch_queue_create("", DISPATCH_QUEUE_SERIAL) |
并发队列 | 自建队列maxConcurrentOperationCount大于1 | dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) |
回到开头的问题,如何利用NSOperationQueue
实现异步操作呢,代码如下:
let operationQueue = NSOperationQueue()
let operation = NSBlockOperation ()
operation.addExecutionBlock { () -> Void in
print("exec block1 -- \(NSThread.currentThread())")
}
operation.addExecutionBlock { () -> Void in
print("exec block2 -- \(NSThread.currentThread())")
}
operation.addExecutionBlock { () -> Void in
print("exec block3 -- \(NSThread.currentThread())")
}
operationQueue.addOperation(operation)
print("操作结束")
得到运行结果如下:
操作结束
exec block1 -- <NSThread: 0x125672f10>{number = 2, name = (null)}
exec block2 -- <NSThread: 0x12556ba40>{number = 3, name = (null)}
exec block3 -- <NSThread: 0x125672f10>{number = 2, name = (null)}
使用NSOperationQueue
来执行任务与之前的区别在于,首先创建一个非主队列。然后用addOperation
方法替换之前的start
方法。刚刚已经说过,NSOperationQueue
会为每一个NSOperation
建立线程并调用他们的start
方法。
观察一下运行结果,所有的NSOperation
都没有在主线程执行,从而成功的实现了异步、并行处理。
#NSOperation新特性
在学习NSOperation
的时候,我们总是用GCD的概念去解释。但是NSOperation
作为对GCD更高层次的封装,它有着一些GCD无法实现(或者至少说很难实现)的特性。由于NSOperation
和NSOperationQueue
良好的封装,这些新特性的使用都非常简单。
###取消任务
如果我们有两次网络请求,第二次请求会用到第一次的数据。如果此时网络情况不好,第一次请求超时了,那么第二次请求也没有必要发送了。当然,用户也有可能人为地取消某个NSOperation
。
当某个NSOperation
被取消时,我们应该尽可能的清除NSOperation
内部的数据并且把cancelled
和finished
设为true
,把executing
设为false
。
//取消某个NSOperation
operation1.cancel()
//取消某个NSOperationQueue剩余的NSOperation
queue.cencelAllOperations()
###设置依赖
依然考虑刚刚所说的两次网络请求的例子。因为第二次请求会用到第一次的数据,所以我们要保证发出第二次请求的时候第一个请求已经执行完。但是我们同时还希望利用到NSOperationQueue
的并发特性(因为可能不止这两个任务)。
这时候我们可以设置NSOperation
之间的依赖关系。语法非常简洁:
operation2.addDependency(operation1)
需要注意的是NSOperation
之间的相互依赖会导致死锁
###NSOperationQueue的暂停与恢复
这个更加简单,只要修改suspended
属性即可
queue.suspended = true //暂停queue中所有operation
queue.suspended = false //恢复queue中所有operation
###NSOperation的优先级
GCD中,任务(block)是没有优先级的,而队列具有优先级。和GCD相反,我们一般考虑NSOperation
的优先级
NSOperation
有一个NSOperationQueuePriority
枚举类型的属性queuePriority
public enum NSOperationQueuePriority : Int {
case VeryLow
case Low
case Normal
case High
case VeryHigh
}
需要注意的是,NSOperationQueue
也不能完全保证优先级高的任务一定先执行。
#NSOperation和GCD如何选择
其实经过这两篇文章的分析,我们大概对NSOperation
和GCD
都有了比较详细的了解,同时在亲自运用这两者的过程中有了自己的理解。
GCD以block为单位,代码简洁。同时GCD中的队列、组、信号量、source、barriers都是组成并行编程的基本原语。对于一次性的计算,或是仅仅为了加快现有方法的运行速度,选择轻量化的GCD就更加方便。
而NSOperation
可以用来规划一组任务之间的依赖关系,设置它们的优先级,任务能被取消。队列可以暂停、恢复。NSOperation
还可以被子类化。这些都是GCD所不具备的。
所以我们要记住的是:
NSOperation
和GCD并不是互斥的,有效地结合两者可以开发出更棒的应用
#总结
到目前为止,我们已经理解了多线程编程的基本概念,GCD的简单使用,用NSOperation
实现GCD的功能,以及NSOperation
的高级特性。在最后一章iOS多线程编程总结(下)中会介绍GCD的底层特性,如barrier、suspend、resume、semaphore等。