iOS开发记录iOS学习开发iOS

iOS中NSOperation详解

2019-03-07  本文已影响2人  HuyaRC

分享是每个优秀的程序员所必备的品质


内容提要:

概念

NSOperation的子类

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种

NSInvocationOperation

直接上代码:

-(void) invocationOpeation {
    
    //1.创建操作,封装任务
    /*
     第三个参数object:前面方法需要接受的参数 可以为nil
     */
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2) object:nil];
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation3) object:nil];
    
    //2.启动|执行操作
    [op1 start];
    [op2 start];
    [op3 start];
}

-(void)operation1{
    NSLog(@"1--%@",[NSThread currentThread]);
}

-(void)operation2{
    NSLog(@"2--%@",[NSThread currentThread]);
}

-(void)operation3{
    NSLog(@"3--%@",[NSThread currentThread]);
}

打印:

RCNSOperationDemo[1368:53571] 1--<NSThread: 0x6000039cb5c0>{number = 1, name = main}
RCNSOperationDemo[1368:53571] 2--<NSThread: 0x6000039cb5c0>{number = 1, name = main}
RCNSOperationDemo[1368:53571] 3--<NSThread: 0x6000039cb5c0>{number = 1, name = main}

是不是觉得没什么软用,还不如不用!NSInvocationOperation只有配合NSOperationQueue使用才能实现多线程编程(后面会说到),单独使用NSInvocationOperation不会开启线程,默认在当前线程(指执行该方法的线程)中同步执行。

NSBlockOperation

和NSInvocationOperation相似,只是将方法替换成了Block,并可以使用addExecutionBlock追加任务
直接上代码:

-(void)blockOperation{
    //1.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    //追加任务
    //注意:如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
    //注意:不一定是子线程,有可能是主线程
    [op3 addExecutionBlock:^{
        NSLog(@"4---%@",[NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"5---%@",[NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"6---%@",[NSThread currentThread]);
    }];
    
    //2.启动
    [op1 start];
    [op2 start];
    [op3 start];
}

打印:

// 第一次打印:
RCNSOperationDemo[1479:60441] 1----<NSThread: 0x600000b0a940>{number = 1, name = main}
RCNSOperationDemo[1479:60441] 2----<NSThread: 0x600000b0a940>{number = 1, name = main}
RCNSOperationDemo[1479:60441] 4---<NSThread: 0x600000b0a940>{number = 1, name = main}
RCNSOperationDemo[1479:60491] 3----<NSThread: 0x600000b5f800>{number = 3, name = (null)}
RCNSOperationDemo[1479:60544] 5---<NSThread: 0x600000b5f780>{number = 4, name = (null)}
RCNSOperationDemo[1479:60543] 6---<NSThread: 0x600000b56480>{number = 5, name = (null)}
// 第n次打印:
RCNSOperationDemo[1479:60441] 1----<NSThread: 0x600000b0a940>{number = 1, name = main}
RCNSOperationDemo[1479:60441] 2----<NSThread: 0x600000b0a940>{number = 1, name = main}
RCNSOperationDemo[1479:60491] 3----<NSThread: 0x600000b5f800>{number = 3, name = (null)}
RCNSOperationDemo[1479:60969] 6---<NSThread: 0x600000b54440>{number = 9, name = (null)}
RCNSOperationDemo[1479:60968] 4---<NSThread: 0x600000b40d80>{number = 8, name = (null)}
RCNSOperationDemo[1479:60441] 5---<NSThread: 0x600000b0a940>{number = 1, name = main}

可以得出单独使用NSBlockOperation和NSInvocationOperation一样,默认在当前线程中同步执行。

但是使用addExecutionBlock追加的任务是并发执行的,如果这个操作中的任务数量大于1,那么会开子线程并发执行任务,并且追加的任务不一定就是子线程,也有可能是主线程。所以上述中任务1、2、3执行是可期的,有序的,但是任务4、5、6是并发执行的,不可控的。

自定义继承自NSOperation的类

自定义一个继承自NSOperation的类RCOperation,在.m中重写main方法即可,main方法中就是要执行的操作,代码如下:

#import "RCOperation.h"

@implementation RCOperation
// 重写main方法,适用于代码量较多,功能较复杂的操作
-(void)main{
    // 执行的任务
    NSLog(@"main---%@",[NSThread currentThread]);
}
@end

调用:

- (void)customOpeation {
    //1.封装操作
    RCOperation *op1 = [[RCOperation alloc]init];
    RCOperation *op2 = [[RCOperation alloc]init];
    
    //2.启动
    [op1 start];
    [op2 start];
}

就是这么简单,下面来点不简单的

NSOperationQueue

NSOperation中的两种队列

NSInvocationOperation和NSOperationQueue组合:

上代码:

- (void)invocationOperationWithQueue {
    //1.创建操作,封装任务
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2) object:nil];
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation3) object:nil];
    
    //2.创建队列
    /*
     GCD:
     串行类型:create & 主队列
     并发类型:create & 全局并发队列
     NSOperation:
     主队列:   [NSOperationQueue mainQueue] 和GCD中的主队列一样,串行队列
     非主队列: [[NSOperationQueue alloc]init]  非常特殊(同时具备并发和串行的功能)
     //默认情况下,非主队列是并发队列
     */
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //3.添加操作到队列中,addOperation方法内部已经调用了[op1 start],不需要再手动启动
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

打印:

RCNSOperationDemo[2036:91039] 3--<NSThread: 0x600002d97580>{number = 4, name = (null)}
RCNSOperationDemo[2036:91040] 2--<NSThread: 0x600002d97680>{number = 5, name = (null)}
RCNSOperationDemo[2036:91036] 1--<NSThread: 0x600002d97500>{number = 3, name = (null)}

创建的队列中的任务默认是异步执行的

NSBlockOperation和NSOperationQueue组合:

上代码:

- (void)blockOperationWithQueue {
    //1.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    //追加任务
    [op2 addExecutionBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    [op2 addExecutionBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    [op2 addExecutionBlock:^{
        NSLog(@"6----%@",[NSThread currentThread]);
    }];
    
    //2.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //3.添加操作到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
    
    //提供一个简便方法,使用Block直接添加任务
    //1)创建操作,2)添加操作到队列中
    [queue addOperationWithBlock:^{
        NSLog(@"7----%@",[NSThread currentThread]);
    }];
}

打印:

RCNSOperationDemo[2101:94606] 1----<NSThread: 0x6000002b7340>{number = 3, name = (null)}
RCNSOperationDemo[2101:94724] 3----<NSThread: 0x6000002ba5c0>{number = 4, name = (null)}
RCNSOperationDemo[2101:94725] 7----<NSThread: 0x6000002ba680>{number = 5, name = (null)}
RCNSOperationDemo[2101:94605] 2----<NSThread: 0x6000002b75c0>{number = 6, name = (null)}
RCNSOperationDemo[2101:94726] 4----<NSThread: 0x6000002ba700>{number = 7, name = (null)}
RCNSOperationDemo[2101:94606] 6----<NSThread: 0x6000002b7340>{number = 3, name = (null)}
RCNSOperationDemo[2101:94727] 5----<NSThread: 0x6000002b7640>{number = 8, name = (null)}

任务都是并发执行的

自定义的Operation和NSOperationQueue组合:
- (void)customWithQueue{
    //1.封装操作
    RCOperation *op1 = [[RCOperation alloc]init];
    RCOperation *op2 = [[RCOperation alloc]init];
  
    //2.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //3.添加操作到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
}
NSOperation其它用法
设置最大并发数【控制任务并发和串行】

最大并发数是队列在同一时间中最多有多少个任务可以执行
static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;默认是-1

-(void)maxConcurrentTest {
    //1.创建队列
    //默认是并发队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2.设置最大并发数量 maxConcurrentOperationCount
    /*
     同一时间最多有多少个任务可以执行
     串行执行任务!=只开一条线程 (线程同步)
     maxConcurrentOperationCount >1 那么就是并发队列
     maxConcurrentOperationCount == 1 那就是串行队列
     maxConcurrentOperationCount == 0  不会执行任务
     maxConcurrentOperationCount == -1 特殊意义 最大值 表示不受限制
     */
    queue.maxConcurrentOperationCount = 1;
    
    //3.封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    //4.添加到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
}

设置最大并发送=1打印:

RCNSOperationDemo[2295:105303] 1----<NSThread: 0x600003496940>{number = 3, name = (null)}
RCNSOperationDemo[2295:105302] 2----<NSThread: 0x6000034addc0>{number = 4, name = (null)}
RCNSOperationDemo[2295:105303] 3----<NSThread: 0x600003496940>{number = 3, name = (null)}
RCNSOperationDemo[2295:105303] 4----<NSThread: 0x600003496940>{number = 3, name = (null)}

可见,设置最大并发送=1,队列是按顺序串行执行任务的。
注意,串行执行任务 != 只开一条线程 ,可以开启多条线程,只不过是以线程同步的方式执行的,就像加了互斥锁,区别队列里的任务是串行执行的还是并发执行的,不是看它开了多少条线程,而是看任务的执行方式,是有序的还是无序的。

队列的暂停和恢复以及取消
// YES代表暂停队列,NO代表恢复队列
- (void)setSuspended:(BOOL)b;

注意:暂停操作不能使当前正在处于执行状态的任务暂停,而是该任务执行结束,后面的任务不会执行,处于排队等待状态 。例如执行2个任务,在执行第1个任务时,执行了暂停操作,第1个任务不会立即暂停,而是第1个任务执行结束后,所有任务暂停,即第2个任务不会再执行。

// 跟暂停相似,当前正在执行的任务不会立即取消,而是后面的所有任务永远不再执行,且该操作是不可以恢复的
- (void)cancelAllOperations;
提示:也可以调用NSOperation的 cancel 方法取消单个操作

以自定义的NSOperation为例,执行一个任务,任务包含3个耗时操作(1个任务中包含3个操作)。
需求:执行取消操作的时候,后面的耗时操作不会再执行(3个耗时操作还是属于同一个任务,区别1个queue包含3个operation)
代码:

RCOperation.h中

#import "RCOperation.h"

@implementation RCOperation
// 重写main方法,适用于代码量较多,功能较复杂的操作
-(void)main{
    
    //耗时操作1
    for (int i = 0; i<1000; i++) {
        // 一般不会讲判断放在耗时操作里面,判断多次,耗费性能
        // if(self.isCancelled) return;
        NSLog(@"任务1-%d--%@",i,[NSThread currentThread]);
    }
    
    //苹果官方建议,每当执行完一次耗时操作之后,就查看一下当前队列是否为取消状态,如果是,那么就直接退出,以此提高程序的性能
    if(self.isCancelled) return;
    
    NSLog(@"+++++++++++++++++++++++++++++++++");
    
    //耗时操作2
    for (int i = 0; i<1000; i++) {
        NSLog(@"任务2-%d--%@",i,[NSThread currentThread]);
    }
    
    if(self.isCancelled) return;
    
    NSLog(@"+++++++++++++++++++++++++++++++++");
    
    //耗时操作3
    for (int i = 0; i<1000; i++) {
        NSLog(@"任务3-%d--%@",i,[NSThread currentThread]);
    }
    
}
@end

执行各种操作代码:

// 开始
- (IBAction)startBtnClick:(id)sender{
    //1.创建队列
    //默认是并发队列
    self.queue = [[NSOperationQueue alloc]init];
    
    //2.设置最大并发数量 maxConcurrentOperationCount
    self.queue.maxConcurrentOperationCount = 1;
    
    RCOperation *op = [[RCOperation alloc]init];
    
    //4.添加到队列
    [self.queue addOperation:op];
}
// 暂停
- (IBAction)suspendBtnClick:(id)sender{
    //设置暂停和恢复
    //suspended设置为YES表示暂停,suspended设置为NO表示恢复
    //暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
    /*
     队列中的任务也是有状态的:已经执行完毕的 | 正在执行 | 排队等待状态
     */
    //不能暂停当前正在处于执行状态的任务
    [self.queue setSuspended:YES];
}
// 继续
- (IBAction)goOnBtnClick:(id)sender{
    //继续执行
    [self.queue setSuspended:NO];
}
// 取消
- (IBAction)cancelBtnClick:(id)sender{
    //取消队列里面的所有操作
    //取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远不再执行,就像后面的所有任务都从队列里面移除了一样
    //取消操作是不可以恢复的
    //该方法内部调用了所有操作的cancel方法
    [self.queue cancelAllOperations];
}

苹果官方建议,每当执行完一次耗时操作之后,就查看一下当前队列是否为取消状态,如果是,那么就直接退出,以此提高程序的性能 。

操作依赖

NSOperation之间可以设置依赖来保证执行顺序,比如:操作A执行完后,才能执行操作B,就可以使用操作依赖
[opB addDependency: opA]; // 操作B依赖于操作A
而且可以在不同queue的NSOperation之间创建依赖关系,比如:操作A在队列1中,操作B在队列2中,也可以使用addDependency还保证执行顺序。

注意:不可以循环依赖:

// 不可以循环依赖
[opB addDependency: opA];
[opA addDependency: opB];

循环依赖的结果:循环依赖的操作都不会有任何的执行,不会发生异常,并且不会影响该队列的其他操作

操作的监听

可以监听一个操作的执行完毕

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

操作依赖和操作的监听使用代码:

- (void)dependencyTest{
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    //2.封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2---%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3---%@",[NSThread currentThread]);
    }];
    
    //操作监听
    op3.completionBlock = ^{
        NSLog(@"3已经执行完了------%@",[NSThread currentThread]);
    };
    
    //添加操作依赖
    [op1 addDependency:op3]; //跨队列依赖,op1属于queue,op3属于queue2
    [op2 addDependency:op1];
    
    //添加操作到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue2 addOperation:op3];
}

打印:

RCNSOperationDemo[3204:155297] 3---<NSThread: 0x60000276c400>{number = 3, name = (null)}
RCNSOperationDemo[3204:155297] 1---<NSThread: 0x60000276c400>{number = 3, name = (null)}
RCNSOperationDemo[3204:155370] 3已经执行完了------<NSThread: 0x60000276c900>{number = 4, name = (null)}
RCNSOperationDemo[3204:155297] 2---<NSThread: 0x60000276c400>{number = 3, name = (null)}

由依赖可知优先级:op3 > op1 > op2,
注意:监听的操作不一定和被监听的操作同一个线程,都是异步的,只是op3执行结束,肯定会执行监听的操作。

NSOperation实现线程间通信

设置操作依赖来实现线程间通信
使用场景:在子线程下载两张图片,下载完毕后绘制在UIImageView中
代码:

- (void)downloadImage{
    
    __block UIImage *image1;
    __block UIImage *image2;
    
    //1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2.封装操作下载图片1
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSURL *url = [NSURL URLWithString:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1907928680,2774802011&fm=26&gp=0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //拿到图片数据
        image1 = [UIImage imageWithData:data];
    }];
    
    
    //3.封装操作下载图片2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1412439743,1735171648&fm=26&gp=0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //拿到图片数据
        image2 = [UIImage imageWithData:data];
    }];
    
    //4.合成图片
    NSBlockOperation *drawOp = [NSBlockOperation blockOperationWithBlock:^{
        
        //4.1 开启图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        //4.2 画image1
        [image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        
        //4.3 画image2
        [image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        
        //4.4 根据图形上下文拿到图片数据
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        //        NSLog(@"%@",image);
        
        //4.5 关闭图形上下文
        UIGraphicsEndImageContext();
        
        //7.回到主线程刷新UI
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            self.imageView.image = image;
            NSLog(@"刷新UI---%@",[NSThread currentThread]);
        }];
        
    }];
    
    //5.设置操作依赖
    [drawOp addDependency:op1];
    [drawOp addDependency:op2];
    
    //6.添加操作到队列中执行
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:drawOp];
}

开启两个异步的子线程来下载图片,添加操作依赖,使2张图片下载结束后,绘制图片。回到主线程显示图片。

RCNSOperationDemo


以上基本上满足NSOperation在日常开发中使用了!

因为最近有小伙伴找我询问关于iOS多线程方面的知识,我怕误人子弟,有遗漏的地方就把以前的东西整理一下,希望可以帮助更多的人!

个人浅见,有误的地方欢迎指正
上一篇下一篇

猜你喜欢

热点阅读