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,头文件如下:

#import 

@interface MyTask : NSOperation

@property(nonatomic,assign) int operationID;

@end

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

#import "MyTask.h"

@implementation MyTask

- (void)main{

  @autoreleasepool {
    NSLog(@"task %i 开始 … ",self.operationID);
    [NSThread sleepForTimeInterval:3];
    NSLog(@"task %i 结束 ",self.operationID);

  }
  
}

@end

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

#import "ViewController.h"
#import "MyTask.h"

@interface ViewController ()

//声明一个NSOperationQueue队列;
@property(nonatomic,strong) NSOperationQueue *queue;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

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

  MyTask *task = [[MyTask alloc] init];
  //设置任务ID为1,区别其他的操作;
  task.operationID = 1;
  //加入到队列中;
  [self.queue addOperation:task];

  //创建一个NSOperation对象(任务),放到NSOperationQueue中,也就是放到一个队列中;
  MyTask *task02 = [[MyTask alloc] init];
  task02.operationID = 2;
  [self.queue addOperation:task02];

}

@end

(4)运行效果如下:

结果1:

iOS多线程开发——NSOperation/NSOperationQueue浅析_第1张图片


结果2:

iOS多线程开发——NSOperation/NSOperationQueue浅析_第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内的代码如下:

#import "ViewController.h"
#import "MyTask.h"

@interface ViewController ()

//声明一个NSOperationQueue队列;
@property(nonatomic,strong) NSOperationQueue *queue;

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


@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

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

  //给队列起一个名字;
  self.queue.name = @"还可以给队列起一个名字";
  //设置队列的最大并发数;
  [self.queue setMaxConcurrentOperationCount:5];

  MyTask *task = [[MyTask alloc] init];
  MyTask *task02 = [[MyTask alloc] init];

  task.operationID = 1;
  [task setQueuePriority:NSOperationQueuePriorityVeryLow];

  task02.operationID = 2;
  //设置优先级;
  [task02 setQueuePriority:NSOperationQueuePriorityVeryHigh];


  //设置任务间的从属关系;task02会在task执行完之后才开始执行;当然也可以移除从属关系;
  [task02 addDependency:task];

  [self.queue addOperation:task];
  [self.queue addOperation:task02];

  //查看当前队列中的所有任务;
  NSArray *operationArr = [self.queue operations];
  NSLog(@"队列中的任务:%@",operationArr);


  //任务1执行完的回调;
  [task setCompletionBlock:^{
    NSLog(@"task结束了");
  }];

  //任务2执行完的回调;
  [task02 setCompletionBlock:^{
    NSLog(@"task02结束了");
  }];


  //取消队列中的suoyou任务;
  [self.queue cancelAllOperations];
}

@end


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

  /**
   *  下面定义一个下载队列;
   */

  NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
  downloadQueue.name = @"下载图片队列";

  [downloadQueue addOperationWithBlock:^{

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

    //在主界面更新UI;
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

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

  }];

运行效果如下:

iOS多线程开发——NSOperation/NSOperationQueue浅析_第3张图片


      温馨提示,在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  。欢迎大家访问!

你可能感兴趣的:(iOS开发,iOS开发技术分享)