iOS开发之多线程编程总结(三)

前言

前段时间的心病落下帷幕后,一大波需求向我迎来,忙的我最近没时间更新博客了,只能在闲暇的时间吹吹牛逼了。这篇博客主要讲解NSOperation的一些知识。

11970779-b26f98e24a80adb1

busy Time.jpg

1. NSOperation简介

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

  • GCD :则是一种更轻量级的,是基于C语言实现的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。
  • Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等。

因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。NSOperation实现多线程的使用步骤分为三步:

  • 1.创建任务:先将需要执行的操作封装到一个NSOperation对象中。
  • 2.创建队列:创建NSOperationQueue对象。
  • 3.将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中。

之后,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

下面就跟我一起学习NSOperation的相关知识点。

2. NSOperation和NSOperationQueue的基本使用

1. 创建任务

在默认情况下,NSOperation 是同步执行的,也就是说会阻塞当前线程直到任务完成。

NSOperation 本身是一个抽象类,不能直接实例化,因此,如果我们想要使用它来执行具体任务的话,就必须使用系统预定义的两个子类NSInvocationOperationNSBlockOperation或者创建自己的子类

  • 1.使用子类NSInvocationOperation
  • 2.使用子类NSBlockOperation
  • 3.定义继承自NSOperation的子类,通过实现内部相应的方法来创建任务。

1.使用子类NSInvocationOperation

NSInvocationOperation:我们可以通过一个 object 和 selector 非常方便地创建一个 NSInvocationOperation ,这是一种非常动态和灵活的方式。

NSInvocationOperation方法:

NSInvocationOperation-Demo:

2.使用子类NSBlockOperation

NSBlockOperation:我们可以使用 NSBlockOperation 来并发执行一个或多个 block ,只有当一个 NSBlockOperation 所关联的所有 block 都执行完毕时(会阻塞当前线程),这个 NSBlockOperation 才算执行完成,有点类似于 dispatch_group 的概念。

NSBlockOperation方法:

NSBlockOperation-Demo:

打印结果:

NSBlockOperation注意点:

  • 从上面打印结果看到在多个线程执行任务。addExecutionBlock:可以为NSBlockOperation添加额外的操作。如果当前NSOperation的任务只有一个的话,那肯定不会开辟一个新的线程,只能同步执行。只有NSOperation的任务数>1的时候,这些额外的操作才有可能在其他线程并发执行。注意我的用词 “才有可能”,也就是说额外的操作也有可能在当前线程里执行。

3. 定义继承自NSOperation的子类

NSOperation的子类:当系统预定义的两个子类 NSInvocationOperationNSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。我们可以自定义非并发和并发两种不同类型的 NSOperation 子类,而自定义一个前者要比后者简单得多。我们先来一个简单的非并发的NSOperation 子类,并发的NSOperation的单独在后面讲解!

非并发的NSOperation 子类Demo:

先定义一个继承自NSOperation的子类,重写main方法
JYSerialOperation.h

JYSerialOperation.m

使用的时候导入头文件然后调用:

2. 创建队列

和GCD中的并发队列、串行队列略有不同的是:NSOperationQueue一共有两种队列:主队列、其他队列。其中其他队列同时包含了串行、并发功能。下边是主队列、其他队列的基本创建方法和特点。

主队列

  • 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

其他队列(非主队列)

  • 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行
  • 同时包含了:串行、并发功能

3. 把任务加入到队列中

只要将任务加入到队列中,就不要执行start方法,队列会负责调度任务自动执行start方法。加入队列的方法如下:

1.- (void)addOperation:(NSOperation *)op;

  • 首先创建任务operation,然后将创建好的任务添加队列!

  • 打印结果:

  • 从上面可以看到NSOperation Queue会开辟线程。然后并发执行!

2.- (void)addOperationWithBlock:(void (^)(void))block ;

  • 无需先创建任务,在block中添加任务,直接将任务block加入到队列中。

  • 打印结果:

可以看出addOperationWithBlock:和NSOperationQueue能够开启新线程,进行并发执行。

3. 队列的重要属性maxConcurrentOperationCount

maxConcurrentOperationCount队列的最大并发数,也就是当前执行队列的任务时,最多开辟多少条线程!具体开多少条线程是由底层线程池来决定。

队列是串行还是并发就是由maxConcurrentOperationCount来决定

  • maxConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。
  • maxConcurrentOperationCount为1时,进行串行执行。
  • maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。

代码参考上一个- (void)addOperationWithBlock:(void (^)(void))block ;Demo,修改最大并发数即可测试,结果如下:

注意点:

  • maxConcurrentOperationCount设置为1时,是串行队列,也有可能开辟多条线程。串行只是一种执行任务的方式,跟开辟线程是不同纬度的概念别弄混了,同步和异步决定开不开线程,可以参考上一篇博客iOS开发之多线程编程总结(二)的基本概念。
  • maxConcurrentOperationCount设置为1时,是串行队列,但是 operation 的执行顺序还是一样会受其他因素影响的,比如 operation 的 isReady 状态、operation 的队列优先级等,如果operation 的执行顺序对我们来说非常重要,那么我们就应该在将 operation 添加到 operation queue 之前就建立好它的依赖关系。

4. 任务的操作依赖

通过配置依赖关系,我们可以让不同的 operation 串行执行,正如我们上面刚刚提到的最大并发数为1时串行执行(但是顺序不一定会是我们想要的顺序),一个 operation 只有在它依赖的所有 operation 都执行完成后才能开始执行。配置 operation 的依赖关系主要涉及到NSOperation 类中的以下两个方法:

  • 特别注意1:addDependency:方法添加的依赖关系是单向的,比如 [A addDependency:B];,表示 A 依赖 B,B 并不依赖 A 。
  • 特别注意2:addDependency:可以跨队列添加依赖,原因:上面添加依赖关系的方法是存在于 NSOperation 类中的,operation 的依赖关系是它自己管理的,与它被添加到哪个 operation queue 无关。
  • 特别注意3:千万不要在 operation 之间添加循环依赖,这样会导致这些 operation 都不会被执行。

任务的操作依赖Demo:

打印结果:

5. 其他方法介绍:

NSOperation方法:

NSOperation Queue方法:

补充知识点:

  • 取消任务:当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。

    The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.

    通常来说,当我们自定义一个 operation 类时,我们需要考虑在以下几个关键点检查 isCancelled 方法的返回值:

    • 在真正开始执行任务之前;
    • 至少在每次循环中检查一次,而如果一次循环的时间本身就比较长的话,则需要检查得更加频繁;
    • 在任何相对来说比较容易中止 operation 的地方。

    看到这里,我想你应该可以意识到一点,那就是尽管 operation 是支持取消操作的,但却并不是立即取消的,而是在你调用了 operation 的 cancel 方法之后的下一个 isCancelled 的检查点取消的。

  • 任务在队列中的优先级:对于被添加到 operation queue 中的 operation 来说,决定它们执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级。operation 的 isReady 状态取决于它的依赖关系,而在队列中的优先级则是 operation 本身的属性。默认情况下,所有新创建的 operation 的队列优先级都是 normal 的,但是我们可以根据需要通过setQueuePriority: 方法来提高或降低 operation 的队列优先级。
    • 优先级只是大概的判断,跟GCD中的全局队列功能相似,并不能依赖这个做严格的任务顺序
    • 队列优先级只应用于相同 operation queue 中的 operation 之间,不同 operation queue 中的 operation 不受此影响
  • completionBlock:一个 operation 可以在它的主任务执行完成时回调一个 completion block 。我们可以用 completion block 来执行一些主任务之外的工作。
    • 当一个 operation 被取消时,它的 completion block 仍然会执行,所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值。
    • 我们也没有办法保证 completion block 被回调时一定是在主线程,理论上它应该是与触发 isFinished 的 KVO 通知所在的线程一致的,所以如果有必要的话我们可以在 completion block 中使用 GCD 来保证从主线程更新 UI 。
  • 暂停和恢复 Operation Queue:暂停执行 operation queue 并不能使正在执行的 operation 暂停执行,而只是简单地暂停调度新的 operation 。另外,我们并不能单独地暂停执行一个 operation ,除非直接 cancel 掉。

6. 并发的NSOperation

在上面创建自定义子类NSOperation任务的时候只是创建了串行的NSOperation子类,只要重写main方法即可。现在我们就来看看如何实现并发的子类NSOperation。

NSOperation有三个状态量isCancelled, isExecutingisFinished.

实现并发(concurrent)的NSOperation步骤:

  1. 重写start()函数
    • 必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现。
  2. 重写main函数
    • 可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰。
  3. isExecuting 和 isFinished
    • 必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化。
    • 在并发情况下系统不知道operation什么时候finished, operation里面的task一般来说是异步执行的, 也就是start函数返回了operation不一定就是finish了, 这个你自己来控制, 你什么时候将isFinished置为YES(发送相应的KVO消息), operation就什么时候完成了。
  4. 重写isConcurrent函数
    • 必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。

并发的NSOperationDemo:

JYConcurrentOperation2.h

JYConcurrentOperation2.m

并发的使用:

并发NSOperation的Demo一些解释:

代码中引入了RunLoop的东西->原因呢:

  • 我们把operation加到了非main queue(或者是在子线程调用的), 那么问题来了, 你会发现NSURLConnection delegate不走了,
  • 主线程会自动创建一个RunLoop来保证程序一直运行. 但子线程默认不创建NSRunLoop, 所以子线程的任务一旦返回, 线程就over了.
  • 上面的并发operation当start函数返回后子线程就退出了, 当NSURLConnection的delegate回调时, 线程已经木有了, 所以你也就收不到回调了. 为了保证子线程持续live(等待connection回调), 你需要在子线程中加入RunLoop, 来保证它不会被kill掉.

结尾:

今天的NSOperation就介绍到这里,里面有不对的地方希望大神们可以提出来,今天提到了RunLoop,大家可以学习一下相关的知识点。同时多线程的NSThread、GCD、NSOperation在这三篇文章中基本上介绍完了。

如果你喜欢请点喜欢,加关注哦^_^


ThreadDemo下载链接

iOS开发之多线程编程总结(一)
iOS开发之多线程编程总结(二)

参考资料:
http://www.cocoachina.com/ios/20150807/12911.html
http://www.jianshu.com/p/ebb3e42049fd

1 5 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部