iOS多线程开发-NSOperation/NSOperationQueue浅析

多线程是我们程序开发中不得不面对的问题。iOS开发中主要有三种多线程实现机制:NSThread,NSOperationQueue,GCD,抽象层次分别增高,抽象层越高,使用就越方面。我在前面的5篇博客中《GCD实践——串行队列/并发队列与iOS多线程详解》等讲解了如何使用GCD,今天我们来学习一下NSOperationQueue的使用。本示例代码提交在  https://github.com/chenyufeng1991/NSOperationQueue

      在OS X10.6 和iOS 4之前,NSOperation/NSOperationQueue不同于GCD,他们使用了完全不同的机制。

      从OS X10.6 和iOS4之后,NSOPeration和NSOperationQueue是建立在GCD上的。作为惯例,苹果推荐使用最高级别的抽象。

      首先了解下什么是NSOperation,NSOperation就是一个操作,准确的说就是一个任务,也相当于一个函数块、block块。然后,任务便会有开始执行(start),取消(cancel),是否取消(isCancel),是否完成(isFinishing),暂停(pause)等状态函数,NSOperation本身就是一个基类,不能直接使用,必须继承它。最重要的是只有被加入到OperationQueue中才会被执行。

       NSOperation中比较重要的是start和main函数,一般结合NSOperationQueue来使用。NSOperationQueue是一个队列,我们需要把一个任务加入到一个队列中。OperationQueue实质上也就是数组管理,对添加进去的operation进行创建、取消、执行等操作。添加到queue中的operation,queue会默认调用operation的start函数来执行任务,而start函数默认又是调用main函数的。

定义一个任务的步骤如下:

(1)继承自NSOperation类;

(2)重写main方法;

(3)在main方法中创建一个autoreleasepool;

(4)把你需要的代码放到autoreleasepool中执行;

     在线程操作中,我们从来不能确定一个线程什么时候开始,会持续多少时间。下面我通过一段简单地代码演示如何使用NSOperation和NSOperationQueue:

(1)定义一个MyTask类,继承自NSOperation,并声明一个属性,用来标识线程ID,头文件如下:

 
  1. #import <Foundation/Foundation.h>

  2.  
  3. @interface MyTask : NSOperation

  4.  
  5. @property(nonatomic,assign) int operationID;

  6.  
  7. @end


(2)在实现文件中重写main方法,在main中的autoreleasepool中写入需要执行的方法,MyTask.m实现如下:

 
  1. #import "MyTask.h"

  2.  
  3. @implementation MyTask

  4.  
  5. - (void)main{

  6.  
  7. @autoreleasepool {

  8. NSLog(@"task %i 开始 … ",self.operationID);

  9. [NSThread sleepForTimeInterval:3];

  10. NSLog(@"task %i 结束 ",self.operationID);

  11.  
  12. }

  13.  
  14. }

  15.  
  16. @end


(3)在ViewController的viewDidLoad方法中创建两个task,并加入到NSOperationQueue中,实现如下:

 
  1. #import "ViewController.h"

  2. #import "MyTask.h"

  3.  
  4. @interface ViewController ()

  5.  
  6. //声明一个NSOperationQueue队列;

  7. @property(nonatomic,strong) NSOperationQueue *queue;

  8.  
  9. @end

  10.  
  11. @implementation ViewController

  12.  
  13. - (void)viewDidLoad {

  14. [super viewDidLoad];

  15.  
  16. self.queue = [[NSOperationQueue alloc] init];

  17.  
  18. MyTask *task = [[MyTask alloc] init];

  19. //设置任务ID为1,区别其他的操作;

  20. task.operationID = 1;

  21. //加入到队列中;

  22. [self.queue addOperation:task];

  23.  
  24. //创建一个NSOperation对象(任务),放到NSOperationQueue中,也就是放到一个队列中;

  25. MyTask *task02 = [[MyTask alloc] init];

  26. task02.operationID = 2;

  27. [self.queue addOperation:task02];

  28.  
  29. }

  30.  
  31. @end


(4)运行效果如下:

结果1:

结果2:

    以上两次截然不同的运行结果印证了我们上述的说法,线程开始的时间是不确定的,并且持续时间也是不一定的。

     下面讲解下NSOperation中的主要方法和属性:

开始(start):一般我们不会重写该方法,当我们把一个任务加入到队列后,会自动调用start方法

从属性(dependency):可以让一个任务从属于其他操作。任何任务都可以从属于任意数量的操作。当一个B任务从属于A任务时,即使调用了B任务的start方法,也会等到A执行结束后才开始执行。这个类似于GCD中的线程组或者信号量实现同步机制。

优先级(priority):有时候你可能希望后台运行的任务有较低的优先级,前台任务有较高的优先级,都可以通过priority来设置。方法为:[task setQueuePriority:];     

    

     下面讲解NSOperationQueue,NSOperationQueue不需要继承,也不需要重写任何方法,只要进行简单的创建即可。还可以给队列起一个名字。

并发操作:队列和线程是两个不同的概念。一个队列可以有多个线程,每个队列中的操作会在所属的线程中运行。如果我们创建了一个并发队列,然后添加三个操作到里面。队列会把这三个操作分别放到三个不同的线程中,然后让所有操作在各自的线程中并发运行。

NSOperationQueue可以设置并发操作的最大并发数。

添加:一个操作一旦被添加到一个队列中,然后队列就会负责这个操作。所以说,什么时候调用操作的start方法,默认是由队列决定的。

待处理:任何时候你可以知道一个队列哪个操作在里面,并且总共有多少操作在里面。

暂停队列:可以通过setSuspended方法来暂停一个队列。这样会暂停所有在队列中的操作。注意:我们只能暂停一个队列,而不是暂停一个操作。

取消:可以同时取消一个队列中的所有操作,执行方法:cancelAllOperations即可。一个NSOperation对象(操作)可以通过哦isCancelled方法判断自己是否被取消。

    在了解了Operation和OperationQueue的基本属性和方法后,我们再来对上述代码进行更新,在代码中尝试使用这些属性。注意内部的注释。ViewController内的代码如下:

 
  1. #import "ViewController.h"

  2. #import "MyTask.h"

  3.  
  4. @interface ViewController ()

  5.  
  6. //声明一个NSOperationQueue队列;

  7. @property(nonatomic,strong) NSOperationQueue *queue;

  8.  
  9. @property (weak, nonatomic) IBOutlet UIImageView *imageView;

  10.  
  11.  
  12. @end

  13.  
  14. @implementation ViewController

  15.  
  16. - (void)viewDidLoad {

  17. [super viewDidLoad];

  18.  
  19. self.queue = [[NSOperationQueue alloc] init];

  20.  
  21. //给队列起一个名字;

  22. self.queue.name = @"还可以给队列起一个名字";

  23. //设置队列的最大并发数;

  24. [self.queue setMaxConcurrentOperationCount:5];

  25.  
  26. MyTask *task = [[MyTask alloc] init];

  27. MyTask *task02 = [[MyTask alloc] init];

  28.  
  29. task.operationID = 1;

  30. [task setQueuePriority:NSOperationQueuePriorityVeryLow];

  31.  
  32. task02.operationID = 2;

  33. //设置优先级;

  34. [task02 setQueuePriority:NSOperationQueuePriorityVeryHigh];

  35.  
  36.  
  37. //设置任务间的从属关系;task02会在task执行完之后才开始执行;当然也可以移除从属关系;

  38. [task02 addDependency:task];

  39.  
  40. [self.queue addOperation:task];

  41. [self.queue addOperation:task02];

  42.  
  43. //查看当前队列中的所有任务;

  44. NSArray *operationArr = [self.queue operations];

  45. NSLog(@"队列中的任务:%@",operationArr);

  46.  
  47.  
  48. //任务1执行完的回调;

  49. [task setCompletionBlock:^{

  50. NSLog(@"task结束了");

  51. }];

  52.  
  53. //任务2执行完的回调;

  54. [task02 setCompletionBlock:^{

  55. NSLog(@"task02结束了");

  56. }];

  57.  
  58.  
  59. //取消队列中的suoyou任务;

  60. [self.queue cancelAllOperations];

  61. }

  62.  
  63. @end


 

     下面我写一个使用NSOperationQueue来请求网络图片的实例,里面并没有定义Operation操作,为了方便,而是使用block代码块的方式来作为Operation。如果你想要定义NSOperation对象,可以吧block中的语句放到operation中的main方法中执行。主要学习的是使用线程来请求网络操作,并在主线程中进行更新。代码如下:

 
  1. /**

  2. * 下面定义一个下载队列;

  3. */

  4.  
  5. NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];

  6. downloadQueue.name = @"下载图片队列";

  7.  
  8. [downloadQueue addOperationWithBlock:^{

  9.  
  10. NSURL *url = [[NSURL alloc] initWithString:@"http://gb.cri.cn/mmsource/images/2007/12/13/sw071213011.jpg"];

  11. NSData *data = [[NSData alloc]initWithContentsOfURL:url];

  12.  
  13. //在主界面更新UI;

  14. [[NSOperationQueue mainQueue] addOperationWithBlock:^{

  15.  
  16. self.imageView.image = [UIImage imageWithData:data];

  17. }];

  18.  
  19. }];


运行效果如下:

      温馨提示,在iOS9开发中,网络请求默认使用了https,如果你按原来的方式进行请求,网络请求会失败,并报下面的错:

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

修改方式需要在Info.plist文件中添加下面两个属性:

NSAppTransportSecurity   Dictionary 里面再添加:

NSAllowsArbitraryLoads    Boolean  Yes ,如图:

github主页:https://github.com/chenyufeng1991  。欢迎大家访问!

猜你喜欢

转载自blog.csdn.net/Listron/article/details/81281506