NSOperation基础知识进阶
来源:互联网 发布:网络管理需求分析 编辑:程序博客网 时间:2024/06/07 19:58
我们在上一篇笔记中整理了NSOperation的一些基础知识,接下来,我们进一步学习它的高级用法。
一、NSOperation进阶
1、队列的最大并发数
我们在讲NSOperationQueue时说过,通过[[NSOperationQueue alloc] init]这种方式得到的队列,它同时具备并发和串行的特征。那么,它在什么时候是并发,什么时候是串行呢?可以按住command键点击进入NSOperationQueue的头文件,它里面有一个很重要的属性能帮助我们控制当前最大的并发数:
maxConcurrentOperationCount的返回值是一个NSInteger类型,并且它不能为0。当它的返回值为1时,队列中所有的任务都是串行执行的。不过,一定要注意,串行执行并不等于只开一条线程;当它的值大于1时,那么队列就是并发队列。而且,我们也看到了,NSOperationQueueDefaultMaxConcurrentOperationCount也就是最大并发数,它的默认值是-1。-1在计算机中往往有着特殊的含义,表示其值不受限制。
2、队列暂停、恢复和取消
在NSOperationQueue的头文件中继续往下看,它里面有一个suspended属性,以及一个- cancelAllOperations方法:
我们可以通过suspend属性来暂停或者恢复队列中的任务。不过,需要注意的是,只能暂停队列中排队等待的任务,不能停止正在执行中的任务。另外,还可以通过- cancelAllOperations方法取消队列中所有的任务,并且,任务一旦取消便不可再恢复。- cancelAllOperations方法的应用场景非常广泛,在自定义NSOperation的时候一定要特别注意,不管- main方法里面有多少个耗时操作,实际上它都只是一个任务,如果要赋予它取消功能,就必须通过cancel属性来判断:
- (void)main { // 耗时操作1 for (int i = 0; i < 1000; i++) { // 打印 NSLog(@"耗时操作1执行了%d次---%@", i, [NSThread currentThread]); } // 判断当前操作是否取消,如果是,就直接返回 if (self.isCancelled) { // 直接返回 return; } // 耗时操作2 for (int i = 0; i < 1000; i++) { // 打印 NSLog(@"耗时操作2执行了%d次---%@", i, [NSThread currentThread]); } // 判断当前操作是否取消,如果是,就直接返回 if (self.isCancelled) { // 直接返回 return; } // 耗时操作2 for (int i = 0; i < 1000; i++) { // 打印 NSLog(@"耗时操作3执行了%d次---%@", i, [NSThread currentThread]); }}
- cancelAllOperations方法内部会调用cancel属性的getter方法。在- main方法的耗时操作后面加入取消判断,一但外面有调用- cancelAllOperations方法,那么- main方法里面封装的任务就会被及时取消。
二、NSOperation操作依赖和监听
1、添加操作依赖
我们之前在讲GCD的时候接触过栅栏函数,也就是说通过相应的手段来控制并发队列中任务执行的顺序。在NSOperation中也有类似这样的概念,也就是通过- addDependency:方法来添加操作依赖。其使用方法非常简单,比如说,A和B分别是已经封装好任务的操作对象,[A addDependency:B]的意思是,在操作对象B执行完毕以后,才能执行操作对象A:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // blockOperation [self blockOperation];}// MARK:- NSBlockOperation的高级用法- (void)blockOperation { // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 封装操作 NSBlockOperation *blcp1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"任务1---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务2 NSLog(@"任务2---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务3 NSLog(@"任务3---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp4 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务4 NSLog(@"任务4---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp5 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务5 NSLog(@"任务5---%@", [NSThread currentThread]); }]; // 添加操作依赖 [blcp3 addDependency:blcp1]; // blcp3依赖于blcp1 [blcp1 addDependency:blcp5]; // blcp1依赖于blcp5 [blcp5 addDependency:blcp2]; // blcp5依赖于blcp2 [blcp2 addDependency:blcp4]; // blcp2依赖于blcp4 // 将所有的任务添加到队列中 [queue addOperation:blcp1]; [queue addOperation:blcp2]; [queue addOperation:blcp3]; [queue addOperation:blcp4]; [queue addOperation:blcp5];}
通过前面的学习,我们知道,在没有添加操作依赖的情况下,上面代码中的任务执行顺序是不确定的。但是,添加完上面的操作依赖之后,任务的执行顺序应该是:blcp4→blcp2→blcp5→blcp1→blcp3。运行程序,看一下是不是这样的:
从上面的运行图可以看出,任务执行的顺序完全符合我们的预期。不过,在添加操作依赖的时候千万要注意,就是一定不能搞成循环依赖(也就是你依赖我,我依赖你)!出现循环依赖虽然不会引发崩溃,但是程序运行以后无响应。比如说,像上面的代码,若是在添加操作依赖代码的最后,再加一句[blcp4 addDependency:blcp3],那么就形成循环依赖了。
其实,添加操作依赖这个方法的功能非常强大。除了在一个队列中可以添加操作依赖之外,多个队列和不同的任务之间也可以添加操作依赖:
// MARK:- NSBlockOperation的高级用法- (void)blockOperation { // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // 封装操作 NSBlockOperation *blcp1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"任务1---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务2 NSLog(@"任务2---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务3 NSLog(@"任务3---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp4 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务4 NSLog(@"任务4---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp5 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务5 NSLog(@"任务5---%@", [NSThread currentThread]); }]; // 添加操作依赖 [blcp3 addDependency:blcp1]; [blcp1 addDependency:blcp5]; [blcp5 addDependency:blcp2]; [blcp2 addDependency:blcp4];// [blcp4 addDependency:blcp3]; // 循环依赖,运行程序以后无响应 // 将所有的任务添加到队列中 [queue addOperation:blcp1]; [queue2 addOperation:blcp2]; // 将blcp2添加到queue2中 [queue addOperation:blcp3]; [queue2 addOperation:blcp4]; // 将blcp4添加到queue2中 [queue addOperation:blcp5];}
在上面的代码中,我们新创建了一个队列queue2,并且将blcp2和blcp4添加到queue2中,其它的操作任然放在queue中,但是,程序运行以后,其结果依然和上面在一个队列中添加操作依赖时一样:
2、监听任务的执行
通常情况下,为了提高用户体验,在某个任务执行完毕以后,要及时的发出通知。因此,必须对任务的执行情况进行监听。在NSOperation中监听任务的执行,可以通过completionBlock属性来实现:
// MARK:- NSBlockOperation的高级用法- (void)blockOperation { // 创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // 封装操作 NSBlockOperation *blcp1 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务1 NSLog(@"任务1---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp2 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务2 NSLog(@"任务2---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp3 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务3 NSLog(@"任务3---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp4 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务4 NSLog(@"任务4---%@", [NSThread currentThread]); }]; NSBlockOperation *blcp5 = [NSBlockOperation blockOperationWithBlock:^{ // 封装任务5 NSLog(@"任务5---%@", [NSThread currentThread]); }]; // 监听任务的执行 [blcp3 setCompletionBlock:^{ // 操作完成以后发出通知 NSLog(@"blcp3执行完毕---%@!", [NSThread currentThread]); }]; blcp5.completionBlock = ^ { // 在blcp5执行完毕以后做一些事情 NSLog(@"blcp5执行完毕---%@!", [NSThread currentThread]); }; // 添加操作依赖 [blcp3 addDependency:blcp1]; [blcp1 addDependency:blcp5]; [blcp5 addDependency:blcp2]; [blcp2 addDependency:blcp4]; // 将所有的任务添加到队列中 [queue addOperation:blcp1]; [queue2 addOperation:blcp2]; // 将blcp2添加到queue2中 [queue addOperation:blcp3]; [queue2 addOperation:blcp4]; // 将blcp4添加到queue2中 [queue addOperation:blcp5];}
通过completionBlock属性来封装一段代码,一旦某个操作任务执行完毕之后,它就会调用这个block代码块中的代码:
需要说明的是,监听任务执行的block代码块和被监听的任务,它们并不一定在同一个线程中执行,它们是并发执行的。
三、NSOperation线程间的通信
我们在前面的笔记中讲NSThread和GCD时,都有讲过线程间的通信,下面,我们就来看一下在NSOperation中如何实现线程间的通信。
回顾一下GCD线程间通信的相关知识,我们是使用嵌套block的方式来实现线程间通信的。在NSOperation中,也可以用嵌套block的方式来实现线程间的通信:
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 点击屏幕下载图片 [self downloadImage];}// MARK:- 演示线程间的通信- (void)downloadImage { // 创建一个非主队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 封装任务 NSBlockOperation *downloadImage = [NSBlockOperation blockOperationWithBlock:^{ // 确定URL地址 NSURL *url = [NSURL URLWithString:@"http://i5qiniu.mtime.cn/pi/2016/12/21/102726.51910822_1000X1000.jpg"]; // 下载图片的二进制数据 NSData *imageData = [NSData dataWithContentsOfURL:url]; // 将二进制数据转换为图片 UIImage *image = [UIImage imageWithData:imageData]; NSLog(@"下载图片---%@", [NSThread currentThread]); // 刷新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 将图片设置到UIImageView控件上去 self.imageView.image = image; NSLog(@"刷新UI---%@", [NSThread currentThread]); }]; }]; // 将任务添加到队列中 [queue addOperation:downloadImage];}
首先,我们使用了一个非主队列,以确保任务在执行过程中会开子线程;其次,使用NSBlockOperation来封装任务,并且在这个block代码块中嵌套block,以便回到主线程中去设置图片并刷新UI。运行程序,看一下实现效果:
从控制台打印出来的线程number来看,我们是在子线程中下载图片的,完了之后再回到主线程中去刷新UI,完成了子线程和主线程之间的通信。
我们在讲《GCD知识进阶》的时候做过一个拼接下载图片的案例,接下来我们用NSOperation的相关知识再做一遍这个实例,进一步强化进程间通信的先关知识。
// MARK:- 点击屏幕执行相应的操作- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 点击屏幕下载图片 [self downloadImages];}// MARK:- 下载两张图片,然后再将它们合成一张- (void)downloadImages { // 创建一个非主队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 下载图片1 NSBlockOperation *downloadImageOne = [NSBlockOperation blockOperationWithBlock:^{ // 确定图片的URL地址 NSURL *urlOne = [NSURL URLWithString:@"http://img05.tooopen.com/images/20160128/tooopen_sy_155633976973.jpg"]; // 下载图片的二进制数据 NSData *imageDataOne = [NSData dataWithContentsOfURL:urlOne]; // 将二进制数据转换为图片 UIImage *imageOne = [UIImage imageWithData:imageDataOne]; // 将下载完的图片imageOne保存到imageOne属性中 self.imageOne = imageOne; NSLog(@"下载图片1的线程为:%@", [NSThread currentThread]); }]; // 下载图片2 NSBlockOperation *downloadImageTwo = [NSBlockOperation blockOperationWithBlock:^{ // 确定图片的URL地址 NSURL *urlTwo = [NSURL URLWithString:@"http://img02.tooopen.com/images/20151127/tooopen_sy_149698679682.jpg"]; // 下载图片的二进制数据 NSData *imageDataTwo = [NSData dataWithContentsOfURL:urlTwo]; // 将二进制数据转换为图片 UIImage *imageTwo = [UIImage imageWithData:imageDataTwo]; // 将下载完的图片imageTwo保存到imageTwo属性中 self.imageTwo = imageTwo; NSLog(@"下载图片2的线程为:%@", [NSThread currentThread]); }]; // 合成图片 NSBlockOperation *spliceImages = [NSBlockOperation blockOperationWithBlock:^{ // 确定绘图区域大小 CGSize imageSize = CGSizeMake(self.view.frame.size.width * 0.8, self.view.frame.size.height * 0.8); // 开启图形上下文 UIGraphicsBeginImageContext(imageSize); // 将第一张图片画到绘图区域的上半部分 [self.imageOne drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height * 0.5)]; // 清空imageOne self.imageOne = nil; // 图片绘制到图形上下文以后就没用了,为了节省空间,需要将它清除 // 将第二张图片画到绘图区域的下半部分 [self.imageTwo drawInRect:CGRectMake(0, imageSize.height * 0.5, imageSize.width, imageSize.height * 0.5)]; // 清空imageTwo self.imageTwo = nil; // 根据图形上下文获取一张绘好的图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 关闭图形上下文 UIGraphicsEndImageContext(); // 回到主线程中刷新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 刷新UI self.imageView.image = image; NSLog(@"刷新UI的线程为:%@", [NSThread currentThread]); }]; }]; // 添加操作依赖 [downloadImageTwo addDependency:downloadImageOne]; [spliceImages addDependency:downloadImageTwo]; // 将任务添加到队列中 [queue addOperation:downloadImageOne]; [queue addOperation:downloadImageTwo]; [queue addOperation:spliceImages];}
上面的代码,最关键的是在添加依赖。只有当两张图片都下载完成了,才有可能把它们合成一张。运行程序,注意看一下控制台打印出来的线程number:
以上就是NSOperation基本应用的相关知识,后面的笔记中会接着整理。相关的代码参见NSOperationAdvanced。
- NSOperation基础知识进阶
- ios开发进阶之多线程02 NSOperation
- maven进阶:基础知识
- C基础知识进阶(上)
- NSOperation
- NsOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- NSOperation
- 多线程之GCD的简单使用
- GCD中常用的函数
- GCD知识进阶
- GCD在单例设计模式中的应用
- 多线程之NSOperation的简单使用
- NSOperation基础知识进阶
- 多线程技术的综合应用
- SDWebImage的基本使用
- SDWebImage框架重要的细节
- NSCache的基本使用
- RunLoop的基础知识
- 《Swift数据结构和算法》读书笔记专题
- 项目基本架构的搭建
- Swift基础知识补充(一)