NSOperation和NSOperationQueue相关

苹果在并发编程方面,除了提供有GCD外,还有NSOperation【操作】NSOperationQueue【操作队列】组合。GCD是基于C的底层API,而NSOperation则是封装GCD实现Objective-C API。NSOperationQueue系统给出了许多api,能够很方便的实现GCD需要大量代码的事情【比如:操作队列可以取消在任务处理队列中的任务】。因此,NSOperationQueue被推荐使用。虽然被推荐使用,但个人认为,还是根据自己的编程习惯。如果用着GCD顺手而且也能完全满足自己开发中的需求。就不必非得换用别的。
本文将通过介绍NSOperation和NSOperationQueue的相关用法演示,做以自己日常学习的总结,也希望能够给看到这篇文章的读者们一个入门介绍。

文章内容目录:
  • 1,NSOperation和NSOperationQueue的介绍。
  • 2,NSInvocationOperation的使用
  • 3,NSBlockOperation的使用
  • 4,自定义Operation的步骤
  • 5,其它
  • 6,总结
1、 NSOperation和NSOperationQueue的介绍。
1.1、NSOperation

NSOperation是系统提供的抽象的基类,我们使用的时候需要使用继承于它的子类。系统为我们提供了两种继承于NSOperation的子类,分别是NSInvocationOperationNSBlockOperation,大多情况下,我们用这两个系统提供的子类就能满足我们并发编程的任务,但是如果你不想用系统提供的这两个类,那么你可以根据自己的需求来自定义自己的操作类。文章后边会结束自定义操作类的时候的步骤和注意事项。

1.2、NSOperationQueue
  • NSOperationQueue【操作队列】:用来存放操作的队列。是由GCD提供的一个队列模型的Cocoa抽象。GCD提供了更加底层的控制,而操作队列则在GCD之上实现了一些方便的功能。
  • NSOperationQueue操作队列中的任务的执行顺序收到任务的isReady【就绪状态】状态和任务的队列优先级影响。这和GCD中的队列FIFO的执行顺序有很大区别。
  • 我们可以通过设置NSOperationQueue最大并发操作数(maxConcurrentOperationCount)来控制任务执行是并发还是串行。
  • NSOperationQueue有两种不通类型的队列:主队列和自定义队列。主队列在主线程上运行,而自定义队列在后台执行。这两种队列中加入的任务都需要用NSOperation的子类来表示。
    注:NSOperation【操作】通俗的讲,就是我们写在程序里的一段代码。
2、 NSInvocationOperation的使用
2.1、手动执行操作。
/**
 在主线程中执行
 */
- (void)executeInMainThread
{
    NSLog(@"创建操作:%@",[NSThread currentThread]);
//1:创建操作。
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
//2:开始执行操作。
    [op start];
}

/**
 在子线程中执行
 */
- (void)executeInNewThread
{
    NSLog(@"创建操作:%@",[NSThread currentThread]);
    [NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
}

- (void)task
{
    NSLog(@"执行操作:%@",[NSThread currentThread]);
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"----%@",[NSThread currentThread]);
    }
}

NSOperation和NSOperationQueue相关_第1张图片
主线程.png
NSOperation和NSOperationQueue相关_第2张图片
子线程.png

由上图运行结果(在主线程和子线程中分别创建操作,并且手动触发)可以看出:手动创建操作并调用start方法触发操作的情况下,操作任务会在创建操作的线程中执行。

2.2、和NSOperationQueue配合使用。
  • 2.2.1、将操作添加到主操作队列。
- (void)addMainOperationQueue
{
//获得主操作队列
   NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
   self.mainQueue = mainQueue;
   NSLog(@"创建添加任务%@",[NSThread currentThread]);
   NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
   [mainQueue addOperation:op1];
   [mainQueue cancelAllOperations];
   NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
   [mainQueue addOperation:op2];
   
   NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
   [mainQueue addOperation:op3];
   
   [op3 cancel];//取消某个操作,可以直接调用操作的取消方法cancel。
   //取消整个操作队列的所有操作,这个方法好像没有效果???。在主队列中,没有用,如果将操作加入到自定义队列的话,在操作没有开始执行的时候,是能够取消操作的。
//    [mainQueue cancelAllOperations];
   
   NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
   [mainQueue addOperation:op4];
}
- (void)task:(NSString *)order
{
   for (NSInteger i = 0; i < 3; i++) {
       NSLog(@"任务:%@----%@",order,[NSThread currentThread]);
   }
}
NSOperation和NSOperationQueue相关_第3张图片
主队列串行执行.png

由上图运行结果可以看出,当将操作添加到主操作队列时,所有操作会按照添加到队列中的先后顺序串行依次执行。任务在主线程中执行。

  • 2.2.2、将操作添加到自定义操作队列。
- (void)addCustomeOperationQueue
{
    NSLog(@"创建添加任务%@",[NSThread currentThread]);
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
     customQueue.maxConcurrentOperationCount = 5;//这个属性的设置需在队列中添加任务之前。任务添加到队列后,如果该任务没有依赖关系的话,任务添加到队列后,会直接开始执行。
    //加入到自定义队列里的任务,可以通过设置操作队列的 maxConcurrentOperationCount的值来控制操作的串行执行还是并发执行。
    
    //当maxConcurrentOperationCount = 1的时候,是串行执行。 maxConcurrentOperationCount > 1的时候是并发执行,但是这个线程开启的数量最终还是由系统决定的,不是maxConcurrentOperationCount设置为多少,就开多少条线程。默认情况下,自定义队列的maxConcurrentOperationCount值为-1,表示并发执行。
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
     [customQueue addOperation:op1];
  
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];

    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
    
    NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
    
    //打断点在op1加入队列前后的状态值。
    ///
   
    ///
  
    [customQueue addOperation:op2];
//    [customQueue cancelAllOperations];//这个方法只能取消还未开始执行的操作,如果操作已经开始执行,那么该方法依然取消不了。
    [customQueue addOperation:op3];
    [customQueue addOperation:op4];
}
NSOperation和NSOperationQueue相关_第4张图片
自定义队列并发.png

当将任务添加到自定义队列的时候,会开启子线程,操作会并发执行。

2.3、添加依赖。
  • 2.3.1、主队列
//依赖关系的设置需要在任务添加到队列之前。
- (void)addDependenceInMain//这个添加依赖的用途有点类似GCD中的队列组
{
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    NSLog(@"创建添加任务%@",[NSThread currentThread]);
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
    
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
 
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
    
    
    NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
    
    [op1 addDependency:op2];//由此更可以看出,如果添加了依赖关系,在主队列串行执行任务,也不是先进先出的规则。而是按照依赖关系的属性执行。  应该把操作的所有配置都配置好后,再加入队列,因为加入队列后,操作就开始执行了,再进行配置就晚了。
    [mainQueue addOperation:op1];
    [mainQueue addOperation:op2];
    [mainQueue addOperation:op3];
    [mainQueue addOperation:op4];
}

NSOperation和NSOperationQueue相关_第5张图片
主队列依赖.png

由上图的运行结果可以看出,虽然操作op1先于op2加入到队列中,但是给op1添加了依赖关系。必须op2执行完毕后,才会执行op1, 这里的操作队列就和GCD中的队列不一样了,GCD队列是遵守FIFO规则,而这里的队列里的操作则是根据依赖关系等决定。这种依赖关系是单向的,op1依赖于op2,op2的执行与op1没有任何关系。 不能设置双向依赖,如果op1依赖op2,op2又反过来依赖op1,则会出现互相等待的死锁情况。

注意:关于操作队列与操作的相关配置都要在操作加入到队列前配置完全,因为操作加入到队列后,就开始执行操作了。此时,再进行操作或队列的设置很有可能达不到预期的效果。

  • 2.3.2、自定义队列
- (void)addDependenceInCustom
{
    NSLog(@"创建添加任务%@",[NSThread currentThread]);
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
    customQueue.maxConcurrentOperationCount = 5;
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
    
    NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
    
    //isReady属性如果为YES的时候,该任务处于就绪状态,就等待系统调度执行。如果任务有依赖关系的话,该任务的isReady属性需要在其所属的依赖任务执行完成后,才为YES。
    //默认情况下,操作的queuePriority(队列优先级)为0 NSOperationQueuePriorityNormal 正常优先级。
    //都处于就绪状态下的操作,才开始按照优先级顺序来执行。 优先级高的是说系统调度的概率会大一些,但是也不能确保完全会按照优先级来,如果要设置操作的执行顺序,最有效也最安全的做法还是设置依赖关系。
    [op1 addDependency:op2];
    [op2 addDependency:op3];
    /*注意:两个任务不能相互依赖,如果相互依赖,则会出现死锁,都执行不了
     [op1 addDependency:op2];
     [op2 addDependency:op1];
     这种相互依赖是错误的。
     */
    op1.queuePriority = NSOperationQueuePriorityVeryHigh;
    op2.queuePriority = NSOperationQueuePriorityHigh;
    op3.queuePriority = NSOperationQueuePriorityLow;
    [customQueue addOperation:op1];
    [customQueue addOperation:op2];
    [customQueue addOperation:op3];
    [customQueue addOperation:op4];
}
NSOperation和NSOperationQueue相关_第6张图片
自定义队列依赖.png

由上图运行结果可以看出,虽然在自定义队列中操作是并发执行的,但如果添加了依赖关系的话,op1依赖于op2,op1要在op2执行完全后,才会执行。

依赖 VS 队列优先级(queuePriority)
操作有个isReady属性,该属性表示操作时否处于就绪状态,处于就绪状态的操作,只要等待系统调度,就会执行。而操作的就绪状态取决于依赖关系,当op1依赖于op2的时候,如果op2还没执行完,op1的isReady = NO,即op1还处于未就绪状态。同处于就绪状态的操作,此时再比较它们的队列优先级(queuePriority),这样才有意义。
队列中会先执行处于就绪状态的操作,即便处于就绪状态的操作的队列优先级低于未就绪的操作。所以,要控制操作之间的执行顺序,需要使用依赖关系。

3、 NSBlockOperation的使用
3.1、手动执行操作。
- (void)executeInMainThread
{
    NSLog(@"创建操作:%@",[NSThread currentThread]);
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        [self task];
    }];
    //NSBlockOperation该操作有个方法能在该操作中持续添加操作任务addExecutionBlock,直到全部的block中的任务都执行完成后,该操作op才算执行完毕。当该操作在addExecutionBlock加入比较多的任务时,该op的block中的(包括blockOperationWithBlock和addExecutionBlock中的操作)会在新开的线程中执行。不一定在创建该op的线程中执行。
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op addExecutionBlock:^{
        [self task:@"add"];
    }];
    [op start];
}
- (void)executeInNewThread
{
    [NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
}
- (void)task
{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"操作执行:%@",[NSThread currentThread]);
    }
}
NSOperation和NSOperationQueue相关_第7张图片
block手动.png

由上图运行结果,可以看出,当一个操作中多次添加任务的话,系统会开启新的线程,并发执行操作中的任务。

相比NSInvocationOperation,NSBlockOperation多了一个后续持续给该操作添加任务的方法,[op addExecutionBlock:^{ }]【注意:针对一个操作,blockOperationWithBlock里的任务和addExecutionBlock里的任务都执行完了,才算该操作执行结束】

3.2、和NSOperationQueue联合使用。
  • 3.2.1:添加到主操作队列
- (void)addMainOperationQueue
{
    NSLog(@"任务创建:%@",[NSThread currentThread]);
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"1"];
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"2"];
    }];

    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"3"];
    }];
 
    [mainQueue addOperation:op1];
    [mainQueue cancelAllOperations];
//    [op2 addDependency:op1];
    [mainQueue addOperation:op2];
    [mainQueue addOperation:op3];
    
    
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];[op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    [op2 addExecutionBlock:^{
        [self task:@"2.1"];
    }];
    [op3 addExecutionBlock:^{
        [self task:@"3.1"];
    }];
    //将操作加入到主队列中后,根据操作添加到队列中的先后顺序(操作之间没有添加依赖关系),串行执行。每个操作addExecutionBlock添加的任务和blockOperationWithBlock中的任务共同组成一个操作。两个block中的操作都执行结束后,一个操作才算结束。
    //虽然将操作加到了NSOperationQueue主操作队列,但是当操作中addExecutionBlock加的任务比较多的时候,操作block中的任务会在新的线程中并发执行,但是对于操作来说,操作时串行执行的。
}
NSOperation和NSOperationQueue相关_第8张图片
主队列串行执行操作.png
  • 3.2.2:添加到自定义操作队列
- (void)addCustomOperationQueue
{
    NSLog(@"任务创建:%@",[NSThread currentThread]);
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"1"];
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"2"];
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"3"];
    }];
    
    
    [customQueue addOperation:op1];
    [customQueue addOperation:op2];
    [customQueue addOperation:op3];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
    
}
NSOperation和NSOperationQueue相关_第9张图片
自定义队列操作并发.png

由运行结果可以看出,当将操作添加到自定义队列的话,如果不设置最大并发数maxConcurrentOperationCount的话,操作是并发执行的。当将maxConcurrentOperationCount设置为1的时候,操作串行执行。

3.3、添加依赖。
- (void)addDependenceInCustom
{
    NSLog(@"任务创建:%@",[NSThread currentThread]);
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"1"];
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"2"];
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [self task:@"3"];
    }];
    
    [op1 addDependency:op2];
    [op3 addDependency:op1];
    [customQueue addOperation:op1];
    [customQueue addOperation:op2];
    [customQueue addOperation:op3];
    [op1 addExecutionBlock:^{
        [self task:@"1.1"];
    }];
}
NSOperation和NSOperationQueue相关_第10张图片
自定义block依赖.png

NSBlockOperation添加依赖的做法和NSInvocationOperation的写法一样,这里需要注意一下的是:由上图也可以看出结果:op3的执行是在op1完全执行后才执行的。而op1中blockOperationWithBlock和addExecutionBlock这两种block里的任务都执行完,才算op1操作执行结束

4、 自定义Operation的步骤

一般来说,系统提供的两个NSOperation的两个子类NSInvocationOperationNSBlockOperation就能满足开发需求了。但是,如果不想用系统提供的类,可以自定义自己的操作类。继承于NSOperation类。最起码要实现两个方法。
1,初始化对象的方法。
2,重写父类的main方法,这个方法里是你的任务代码。

  • 定义
#import "CustomOperation.h"

@interface CustomOperation()
@property (nonatomic,strong)id data;//作为该操作的参数。
@end

@implementation CustomOperation
- (instancetype)initWithData:(id)data
{
    if (self = [super init]) {
        self.data = data;
    }
    return self;
}
- (void)main//只重写了这个方法的话,如果单独手动执行该自定义操作的话,操作时同步执行的,如果操作队列联合起来使用的话,也会并发执行操作。
{
    NSString *order = (NSString *)self.data;
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"自定义操作%@----:%@",order,[NSThread currentThread]);
    }
}
@end
  • 使用
- (void)executeInMain
{
    CustomOperation *op = [[CustomOperation alloc]initWithData:@""];
    [op start];
}

//和操作队列联合使用
- (void)addMainQueue
{
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
    CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
    CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
    
    [mainQueue addOperation:op1];
    [mainQueue addOperation:op2];
    [mainQueue addOperation:op3];
}

- (void)addCustomQueue
{
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
    CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
    CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
    CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
    
    [customQueue addOperation:op1];
    [op1 cancel];
    [customQueue addOperation:op2];
    [customQueue addOperation:op3];
}
5、 其它
  • NSOperation的一些常用属性和方法。
//操作是否取消
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;//取消操作
@property (readonly, getter=isExecuting) BOOL executing;//是否执行
@property (readonly, getter=isFinished) BOOL finished;//是否执行结束
@property (readonly, getter=isAsynchronous) BOOL asynchronous //是否异步
@property (readonly, getter=isReady) BOOL ready;//是否处于就绪状态
- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//去除依赖
@property (readonly, copy) NSArray *dependencies;//所有相关依赖
- (void)waitUntilFinished;//执行该操作的时候阻塞当前线程,直到该操作执行
结束。
- (void)addOperationsInCustom
{
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait1"];
       NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait2"];
       NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait3"];
       NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait4"];
    NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
//    [customQueue addOperation:op1];
//    [op1 waitUntilFinished];//waitUntilFinished是阻塞当前线程的作用,在这里会阻塞主线程,阻塞主线程中继续往队列中加任务,直到该op1操作执行结束,这样就能实现操作的串行了。
      [customQueue addOperation:op2];
     [op2 waitUntilFinished];
    [customQueue addOperation:op3];
    [op3 waitUntilFinished];
    [customQueue addOperation:op4];
}
  • NSOperationQueue的相关属性和方法
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait ;//可以根据wait参数设置队列中操作的执行方式是串行还是并发。
- (void)addOperationWithBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount ;//操作的个数
@property NSInteger maxConcurrentOperationCount;//最大并发数
@property (getter=isSuspended) BOOL suspended;//是否悬挂
- (void)cancelAllOperations;//取消队列中的所有操作。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue;//操作队列对应的GCD队列。
NSOperation和NSOperationQueue相关_第11张图片
队列的GCD.png
6、 总结

至此;非常感谢您看到这里。
自己总结了一下NSOperation相关的知识点,也又重新理顺了自己的一些想法。但是,这些知识点不用的话,还是会很快忘记的,最好的理解和掌握是在运用在实践中。在以后的实践运用中如果有新内容或新的想法,会持续更新。
文章中的源码已经上传。Demo传送
如有疑问或错误,欢迎指正和提问。谢谢!!!
参考
非常感谢以下文章的作者。谢谢。
iOS多线程:『NSOperation、NSOperationQueue』详尽总结

iOS 并发编程之 Operation Queues

你可能感兴趣的:(NSOperation和NSOperationQueue相关)