上篇文章学了 GCD 的基本使用,本文就来学习一下 iOS 的另一套多线程解决方案:NSOperation,它是基于 GCD 开发的,但是比 GCD 更加面向对象,并且拥有更强的可控性和代码可读性,NSOperation 是一个抽象基类,我们实际使用的是系统封装好的两个子类 NSInvocationOperation
和 NSBlockOperation
,NSOperation 需要配合NSOperationQueue 来实现多线程,默认情况下,NSOperation 单独使用时系统同步执行操作,不会开启新新线程,只有配合NSOperationQueue 才能实现任务的异步执行,NSOperation 和NSOperationQueue 实现多线程的具体步骤:
- 创建任务:先将需要执行的任务封装到一个 NSOperation 对象中
- 创建队列:创建一个 NSOperationQueue 对象
- 任务加入队列:然后将 NSOperation 对象添加到 NSOperationQueue 对象中
之后,系统会⾃动将 NSOperationQueue 中的 NSOperation 任务取出,放入新线程中执⾏,下面看看详细的 NSOperation 和 NSOperationQueue 的详细使用
NSOperation 和 NSOperationQueue
Swfit 中 NSOperation
和 NSOperationQueue
都去掉了前缀 NS
,直接叫 Operation
和 OperationQueue
,下文仍然沿用 OC 的叫法,明白意思就行
NSInvocationOperation
苹果认为 NSInvocationOperation 不是类型安全或者不是 ARC 安全的,在 Swift中 取消了与之相关的 API,如果你使用 Swift 那么就只能使用 NSBlockOperation,所以这里只演示 OC 如何使用 NSInvocationOperation 创建操作对象
|
|
NSBlockOperation
使用 NSBlockOperation 创建一个操作对象
|
|
打印结果:
|
|
可以看到,在没有使用 NSOperationQueue,单独使用 NSBlockOperation 的情况下,NSBlockOperation 是在主线程执行操作,并没有开启新线程
来看一下另外一种情况
|
|
打印结果:
|
|
NSBlockOperation 还提供了一个方法 addExecutionBlock
,通过 addExecutionBlock
添加的任务会在其他线程中并发执行
NSOperationQueue
NSOperationQueue 不同于 GCD 的队列,它只有两种队列:主队列和其他队列,主队列对应的就是主线程队列,其他队列用来实现串行和并发的功能,只要操作对象添加到队列,就会自动自动调用操作对象的 start()
方法
创建队列
|
|
把操作任务添加到队列有两种方法:第一种,先创建任务操作对象,然后使用 addOperation()
把任务操作对象添加到队列,第二种,我们也可以不创建任务操作对象,直接使用队列添加任务
|
|
打印结果:
|
|
可以发现不管把任务操作对象添加到队列还是直接把任务添加到队列,都是并发的执行任务,当然如果是主队列的话,任务还是串行的在主线程执行的,那如果需要开启新线程异步串行呢?NSOperationQueue 有个属性 maxConcurrentOperationCount
:最大并发数
maxConcurrentOperationCount
默认情况下为 -1,表示不进行限制,即默认为并发执行maxConcurrentOperationCount
为1时,进行串行执行。当maxConcurrentOperationCount
大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整
注意:
maxConcurrentOperationCount
设置的是队列里面最多能并发运行的操作任务个数,而不是线程个数,当一个线程执行完毕后会有一个回收到线程池的过程,这时如果线程池中还有别的线程就会直接拿出来进行任务的执行,如果线程池中没有线程,就会等待回收后的线程
|
|
最大并发数为1,打印结果:
|
|
最大并发数为2,打印结果:
|
|
可以看到当最大并发数为1时,任务是按顺序串行执行的,当最大并发数为2时,任务是并发执行的,顺序随机,但是开启线程的数量是由系统决定的,设为1就表示队列每次只能执行一个操作,因此串行化的NSOperationQueue 并不等同于GCD中的串行队列
NSOperation 和 NSOperationQueue 的其他用法
NSOperation 操作依赖
当某个 NSOperation 对象依赖于其它 NSOperation 对象的完成时,就可以通过 addDependency()
方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前 NSOperation 对象才会开始执行操作,另外,可以通过 removeDependency()
方法来删除依赖对象
|
|
打印结果:
|
|
不管运行多少次,最终的打印结果都是:任务2 -> 任务1 -> 任务3
注意:不能添加相互依赖,会死锁,比如 A依赖B,B依赖A,可以在不同的队列之间依赖,依赖是添加到任务身上的,和队列没关系
取消 NSOperation
NSOperation 对象一旦添加到 NSOperationQueue 队列,这个队列就拥有了这个 NSOperation 对象,并且不能被删除,唯一能做的事情是取消,可以调用 NSOperation 对象的 cancel()
方法取消单个任务操作,也可以调用 NSOperationQueue 队列的cancelAllOperations()
方法取消当前队列中的所有操作
|
|
等待 NSOperation 完成
如果需要在当前线程中处理操作任务完成后的结果,可以使用NSOperation 的waitUntilFinished()
方法阻塞当前线程,等待操作任务的完成,最好不要在主线程调用这个方法,这可能导致阻塞主线程影响用户体验
|
|
除了等待单个操作对象任务完成,也可以同时等待一个队列的所有操作完成,使用 NSOperationQueue 的 waitUntilAllOperationsAreFinished()
方法,在等待一个队列时,应用的其他线程仍然可以往队列里添加操作任务对象,这样可能会加长等待时间
|
|
暂停和继续 NSOperationQueue
如果你想临时暂停操作任务的执行,可以设置队列的 isSuspended
为 true
,不过暂停一个队列不会影响当前的正在执行的操作任务,不过会停止新的操作对象执行,可以在响应用户请求的时候,暂停一个等待中的任务,完成用户的请求之后,再次调用 isSuspended
继续队列中的其他操作任务的执行
|
|
NSOperation 到这里差不多就讲完了,本文只是提供了NSOperation 的各种方法的使用,实际开发中具体怎么把他们用到合适的地方,就需要多多实践了,有机会我写写多线程的实践案例
其他相关文章:
本人刚开始写博客,主要是为了给自己的知识点做一个笔记,方便自己以后查阅,如果能让别人有所启发也是荣幸之至!如有错误,欢迎指正!