3.多线程NSOperation
1.NSOperation的基本操作
使用NSOperation的两个子类,NSInvocationOperation 和 NSBlockOperation 创建操作,然后将操作添加到队列中去执行
// NSOperation // 1. 实例化 NSOperation 子类对象:NSInvocationOperation NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test1) object:nil]; // 2. 实例化 NSOperation 子类对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ // NSLog(@"耗时操作2------------"); }]; // NSOperationQueue // 1.获取主队列 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; // 2.创建非主队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // NSOperation使用: 将操作添加到队列中! [mainQueue addOperation:op]; [queue addOperation:op1];
2.NSOperation定义的操作可以直接用start启动,相当于直接执行,入口是NSOperation中定义的main方法
// 1. 实例化操作对象 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test1) object:nil]; NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test2) object:nil]; NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test3) object:nil]; // 需求: 1. 三个操作都是耗时操作! // // 2. 创建非主队列 // NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // // // 3. 将操作添加到非主队列中 // [queue addOperation:op1]; // [queue addOperation:op2]; // [queue addOperation:op3]; // // 需求: 2. 三个操作都是UI操作 // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; // // 相当于 GCD 中的 异步函数 + 主队列! // [mainQueue addOperation:op1]; // [mainQueue addOperation:op2]; // [mainQueue addOperation:op3]; // NSOperation 执行方式2: 直接启动!直接在当前线程执行! [op1 start]; [op2 start]; [op3 start]; // NSOperation 对象的入口是定义在自身内部的 main 方法; // 当将操作添加到操作队列中或者直接调用操作的 start 方法之后,内部都会调用 main 方法,所有两者都能够执行操作! NSLog(@"touchesEnd"); } - (void)test1 { NSLog(@"test1----------%@",[NSThread currentThread]); } - (void)test2 { NSLog(@"test2----------%@",[NSThread currentThread]); } - (void)test3 { NSLog(@"test3----------%@",[NSThread currentThread]); }
3.使用block
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"222222222----%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"33333333----%@",[NSThread currentThread]); }]; // 1.将操作添加到主队列中 // [[NSOperationQueue mainQueue] addOperation:op1]; // [[NSOperationQueue mainQueue] addOperation:op2]; // [[NSOperationQueue mainQueue] addOperation:op3]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
4.向blockOperation中追加任务
// 当 NSBlockOperation中的任务数 > 1 之后, 无论是将操作添加到主线程,还是在主线程直接执行 start ,NSBlockOperation中的任务执行顺序都不确定,执行线程也不确定!
// 一半在开发的时候,要避免向 NSBlockOperation 中追加任务!
// 如果任务都是在子线程中执行,并且不需要保证执行顺序,可以直接追加任务!
// 1. 实例化操作对象 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"11111111----%@",[NSThread currentThread]); }]; // 往当前操作中追加操作 [op1 addExecutionBlock:^{ NSLog(@"追加任务1%@",[NSThread currentThread]); }]; // 往当前操作中追加操作 [op1 addExecutionBlock:^{ NSLog(@"追加任务2%@",[NSThread currentThread]); }]; // 当 NSBlockOperation中的任务数 > 1 之后, 无论是将操作添加到主线程,还是在主线程直接执行 start ,NSBlockOperation中的任务执行顺序都不确定,执行线程也不确定! // 一半在开发的时候,要避免向 NSBlockOperation 中追加任务! // 如果任务都是在子线程中执行,并且不需要保证执行顺序,可以直接追加任务!
5.直接通过操作队列添加任务
// 直接通过操作队列添加任务! //将block中的内容当做一个操作添加到主队列中! [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"---------1%@",[NSThread currentThread]); }]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"---------2%@",[NSThread currentThread]); }]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"---------3%@",[NSThread currentThread]); }]; [[[NSOperationQueue alloc] init] addOperationWithBlock:^{ NSLog(@"---------4%@",[NSThread currentThread]); }]; [[[NSOperationQueue alloc] init] addOperationWithBlock:^{ NSLog(@"---------5%@",[NSThread currentThread]); }]; [[[NSOperationQueue alloc] init] addOperationWithBlock:^{ NSLog(@"---------6%@",[NSThread currentThread]); }];
6.给操作添加操作依赖,保证操作的顺序执行,避免循环依赖
// NSOperation 相对于 GCD 来说,增加了以下管理线程的功能: // 1. NSOperation可以添加操作依赖: 保证操作的执行顺序! --> 和GCD中将任务添加到一个串行队列中是一样的! 一个串行队列会对应一条线程! // GCD 中的按顺序执行(串行队列)----> 串行执行! // 添加操作依赖之后,系统有可能串行执行保证任务的执行顺序,还有可能采用线程同步技术,保证任务执行顺序! NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"-------222 %@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"-------333 %@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"-------444 %@",[NSThread currentThread]); }]; // 四个操作都是耗时操作! 并且要求按顺序执行! 操作2是UI操作! // 添加操作依赖的注意点: // 1. 不要添加循环依赖! // 2. 一定要在将操作添加到操作队列中之前添加操作依赖! // 优点: 对于不同操作队列中的操作,操作依赖依然有效! // 添加操作依赖! [op2 addDependency:op1]; [op3 addDependency:op2]; [op4 addDependency:op3]; // [op2 addDependency:op3]; // [op1 addDependency:op4]; // 将操作添加到操作队列中 NSOperationQueue *quque = [[NSOperationQueue alloc] init]; [quque addOperation:op3]; [quque addOperation:op1]; [quque addOperation:op4]; // 将操作2 添加到主队列中 [[NSOperationQueue mainQueue] addOperation:op2];
7.NSOperation的高级操作,暂停/恢复/取消/设置最大线程数
// 遇到并发编程,什么时候选择 GCD ,什么时候选择 NSOperation!
// 1.简单的开启线程/回到主线程,选择 GCD : 效率更高,简单!
// 2.需要管理操作(考虑到与用户交互!),使用 NSOperation!
// NSOperation高级操作: // 1. 添加操作依赖! // 2. 管理操作: 重点! 是操作队列的方法! // 2.1 暂停/恢复 取消 操作! // 2.2 开启合适的线程数量!(最多不超过6条!) // 一半开发的时候,会将操作队列设置成一个全局的变量(属性)! // NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"0----------"); [self test]; }]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ [self test]; }]; [queue addOperation:op]; // 1. 暂停操作// 开始滚动的时候 [queue setSuspended:YES]; // 2. 恢复操作// 滚动结束的时候 [queue setSuspended:NO]; // 3. 取消所有操作// 接收到内存警告 [queue cancelAllOperations]; // 3补充: 取消单个操作!是操作的方法! [op cancel]; // 设置最大并发数,开启合适的线程数量 // 实例化操作队列的时候 [queue setMaxConcurrentOperationCount:6]; // 遇到并发编程,什么时候选择 GCD ,什么时候选择 NSOperation! // 1.简单的开启线程/回到主线程,选择 GCD : 效率更高,简单! // 2.需要管理操作(考虑到与用户交互!),使用 NSOperation!
8.block的循环引用问题,使用__weak typeof(self)weakself = self;弱引用
#import "HMViewController.h" @interface HMViewController () // 全局操作队列 @property(nonatomic ,strong)NSOperationQueue *queue; // @property(nonatomic ,strong)NSMutableArray *array; @end @implementation HMViewController -(NSOperationQueue *)queue { if (!_queue) { _queue = [[NSOperationQueue alloc] init]; [_queue setMaxConcurrentOperationCount:6]; } return _queue; } -(NSMutableArray *)array { if (!_array) { _array = [NSMutableArray array]; } return _array; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor orangeColor]; // NSLog(@"控制器创建成功!"); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"touchesBegan"); // block! // 1. 创建操作 // 为了防止 block 中使用 self 出现的循环引用问题! 一般在 block中使用 self 的时候,要使用 self 的弱引用!!! // 为了安全,block中出现self,都使用 弱引用写法! // weakself : 下面就是 self 的弱引用写法! __weak typeof(self)weakself = self; // __weak HMViewController *wself = self; NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ [self test]; }]; // 将操作添加到 array 中! [self.array addObject:op]; // self --> queue -->op --> block --> self : 循环引用链条! // 2. 将操作添加到操作队列中 // 因为操作执行完毕之后,操作队列会自动释放其中的操作,所以,在操作中(NSblockOperation)block里有了 self,没有关系,能够释放.不会造成循环引用! [self.queue addOperation:op]; } -(void)test { NSLog(@"------%@",[NSThread currentThread]); } -(void)dealloc { NSLog(@"控制器销毁了!"); }
9.线程间通信,在子线程下载图片,在主线程更新UI
// 1 在子线程下载图片 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 如果图片地址中出现了 & 符号,换一张图片! // image :下载好的网络图片 UIImage *image = [self downloadWebImageWithUrlString:@"http://pic14.nipic.com/20110522/7411759_164157418126_2.jpg"]; // 回到主线程! [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 显示图片 self.imageView.image = image; }]; }]; [self.queue addOperation:op]; NSLog(@"touchesEnd"); } // 下载网络图片的方法 - (UIImage *)downloadWebImageWithUrlString:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; // 下载方法!耗时方法! NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; return image; }
10.自定义NSOperation,继承NSOperation,重写mian方法
加自动释放池
// // HMDownloadOperation.h // 09-线程间通信 // // Created by HM on 16/1/25. // Copyright © 2016年 HM. All rights reserved. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @class HMDownloadOperation; @protocol HMDownloadOperationDelegate <NSObject> -(void)downloadImageWithOperation:(HMDownloadOperation *)operation; @end @interface HMDownloadOperation : NSOperation // 代理属性 @property(nonatomic ,weak) id<HMDownloadOperationDelegate> delegate; // 写一个图片属性// 下载好的图片 @property(nonatomic ,strong) UIImage *image; // 图片下载地址 @property(nonatomic ,copy)NSString *urlString; @end
// // HMDownloadOperation.m // 09-线程间通信 // // Created by HM on 16/1/25. // Copyright © 2016年 HM. All rights reserved. // #import "HMDownloadOperation.h" @implementation HMDownloadOperation // 重写 NSOperation 的 main 方法! // 当把自定义的操作添加到操作队列中,或者直接调用 操作的 start 方法之后,都会自动来执行 main 方法中的内容! -(void)main { // 为了能够及时释放内存,一般会手动书写一个 autoreleasepool!苹果官方文档不要求写! @autoreleasepool { NSLog(@"----------%@",[NSThread currentThread]); self.image = [self downloadWebImageWithUrlString:self.urlString]; NSLog(@"image:%@",self.image); // 通知/代理/block :为了保证在不同对象之间传值的准确性!采用的是同步传值的方法! // 回到主线程,执行代理方法: dispatch_async(dispatch_get_main_queue(), ^{ // 执行代理方法 if ([self.delegate respondsToSelector:@selector(downloadImageWithOperation:)]) { [self.delegate downloadImageWithOperation:self]; } }); } } // 下载网络图片的方法 - (UIImage *)downloadWebImageWithUrlString:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; // 下载方法!耗时方法! NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; return image; } @end
11.操作完成之后的回调completionBlock
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"--------%@",[NSThread currentThread]); }]; // 操作完成之后的回调! // op.completionBlock = ^(){ // // NSLog(@"操作执行完毕!"); // // }; [op setCompletionBlock:^{ NSLog(@"操作执行完毕!"); }]; [op start];
我要青春像陈孝正一样,不能有一毫米的误差!
我要青春像合伙人一样,为了自尊而战!