NSOperation NSOperationQueue
一. 简介
除了使用NSThread和GCD可以实现多线程,配合使用NSOperation和NSOperationQueue也能实现多线程。
使用NSOperation和NSOperationQueue实现多线程的操作步骤:
1. 将需要执行的操作封装到NSOperation的子类对象中。
实际上,NSOperaion是一个抽象类,并不可以封装操作,必须使用它的子类
2. 将NSOperation对象添加到NSOperationQueue中
3. 系统会自动将NSOperationQueue中的NSOperation取出
4. 将取出的NSOperation封装的操作放到一个新线程中执行。
二. NSOperation的使用
1. 实现NSOperation的封装有三种:
(1)NSInvocationOperation
创建NSInvocationOperation对象,调用start方法开始执行操作。
默认情况下,调用start方法后并不会开启一个新线程去执行操作,而是在当前线程同步执行操作。
只有将NSOperation操作放到NSOperationQueue中,才会异步执行操作。
- (void)viewDidLoad { [super viewDidLoad]; NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil]; [operation1 start]; NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil]; [operation2 start]; } - (void)startOperation1: (id)sender { NSLog(@"operation1---------%@", [NSThread currentThread]); } - (void)startOperation2:(id)sender { NSLog(@"operation2---------%@", [NSThread currentThread]); } // 打印结果: 2017-02-24 12:58:11.696 OperationDemo[74424:8270352] operation1---------<NSThread: 0x7a86af70>{number = 1, name = main} 2017-02-24 12:58:11.698 OperationDemo[74424:8270352] operation2---------<NSThread: 0x7a86af70>{number = 1, name = main}
- (void)viewDidLoad { [super viewDidLoad]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil]; [queue addOperation:operation1]; NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil]; [queue addOperation:operation2]; } - (void)startOperation1: (id)sender { NSLog(@"operation1---------%@", [NSThread currentThread]); } - (void)startOperation2:(id)sender { NSLog(@"operation2---------%@", [NSThread currentThread]); } // 打印结果: 2017-02-24 13:13:19.351 OperationDemo[74639:8308964] operation2---------<NSThread: 0x7b6a2140>{number = 3, name = (null)} 2017-02-24 13:13:19.351 OperationDemo[74639:8308962] operation1---------<NSThread: 0x7b8e5ee0>{number = 2, name = (null)}
(2)NSBlockOperation
创建NSBlockOperation对象,使用addExecutionBlock:方法添加更多的操作。
只要NSBlockOperation封装的操作数大于1,就会异步执行操作。
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation1---------%@", [NSThread currentThread]); }]; [operation start]; // 打印结果: 2017-02-24 13:20:39.094 OperationDemo[74785:8328635] operation1---------<NSThread: 0x78e315f0>{number = 1, name = main}
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation1---------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"operation2---------%@", [NSThread currentThread]); }]; [operation addExecutionBlock:^{ NSLog(@"operation3---------%@", [NSThread currentThread]); }]; [operation start]; // 打印结果: 2017-02-24 13:17:58.073 OperationDemo[74742:8321309] operation3---------<NSThread: 0x79072250>{number = 3, name = (null)} 2017-02-24 13:17:58.073 OperationDemo[74742:8321307] operation2---------<NSThread: 0x78fb8840>{number = 2, name = (null)} 2017-02-24 13:17:58.073 OperationDemo[74742:8321024] operation1---------<NSThread: 0x78f8e020>{number = 1, name = main}
(3)自定义子类继承NSOperation,实现内部相应的main方法封装操作
需要重写main方法,将操作任务放到main方法中。
@implementation CustomOperation - (void)main { // 将操作任务放在这里 NSLog(@"-----1-----"); } @end
三. NSOperationQueue
1. NSOpeationQueue作用:
NSOperation可以调用start方法执行任务,但默认是同步执行的。
如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行操作。
2. 添加NSOperation到NSOperationQueue有两个方法:
-(void)addOperation:(NSOperation *)operation;
-(void)addOperationWithBlock:(void(^)(void))block;
只要将一个操作添加到队列(默认是并行队列),那么队列内部会自动调用start方法。
如果要将队列设置为串行,只需要设置队列的queue.maxConcurrentOperationCount = 1即可。
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil]; [queue addOperation:operation1]; NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil]; [queue addOperation:operation2];
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ NSLog(@"operation1-------%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"operation2-------%@", [NSThread currentThread]); }]; // 打印结果: 2017-02-24 13:30:52.198 OperationDemo[74890:8354590] operation2-------<NSThread: 0x7b6897d0>{number = 3, name = (null)} 2017-02-24 13:30:52.198 OperationDemo[74890:8354591] operation1-------<NSThread: 0x7b750aa0>{number = 2, name = (null)}
四. NSOperationQueue的串行和并发: 最大并发数
队列的最大并发数为maxConcurrentOperationCount;
默认情况下,maxConcurrentOperationCount = -1,代表不限制最大并发数,可以创建N多个线程;
通过[[NSOperationQueue alloc] init] 创建的NSOperationQueue是并发的,如果想设置为串行,只需要将maxConcurrentOperationCount = 1;
不可以将maxConcurrentOperationCount设置为0,否则任务将不会执行。
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; [queue addOperationWithBlock:^{ NSLog(@"operation1-------%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"operation2-------%@", [NSThread currentThread]); }]; //打印结果: 2017-02-24 13:36:23.018 OperationDemo[74951:8368501] operation1-------<NSThread: 0xa22dc70>{number = 2, name = (null)} 2017-02-24 13:36:23.019 OperationDemo[74951:8368501] operation2-------<NSThread: 0xa22dc70>{number = 2, name = (null)}
五. NSOperationQueue的取消、暂停、恢复
可以通过NSOperation的cancel方法来取消单个操作。
操作只要取消,就不会再恢复。
取消任务和暂停任务一样,不会取消当前正在执行的操作,职能取消还未执行的操作。
可以通过NSOperationQueue的cancelAllOperations方法来取消所有的操作。
暂停和恢复任务:
-(void)setSuspended:(Bool)suspend; YES表示暂停,NO表示恢复。
-(Bool)isSuspended; 判断队列是否在暂停中
若要取消的任务是自定义的操作队列的话,执行完一个耗时操作后,需要增加是否取消任务的判断,再去执行另外一个操作任务。
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1; NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation1------%@", [NSThread currentThread]); }]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation2------%@", [NSThread currentThread]); }]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation3------%@", [NSThread currentThread]); }]; [queue addOperation:operation1]; [queue addOperation:operation2]; [queue addOperation:operation3]; [operation3 cancel]; // 打印结果: 2017-02-24 13:43:43.052 OperationDemo[75069:8388603] operation1------<NSThread: 0x79466810>{number = 2, name = (null)} 2017-02-24 13:43:43.054 OperationDemo[75069:8388603] operation2------<NSThread: 0x79466810>{number = 2, name = (null)}
@implementation CustomOperation - (void)main { for (int i = 0; i < 10000; i ++) { NSLog(@"-----1-----%@-----", [NSThread currentThread]); } // 增加队列是否取消任务的判断 if (self.isCancelled) { return; } for (int i = 0; i < 10000; i ++) { NSLog(@"-----2-----%@-----", [NSThread currentThread]); } // 增加队列是否取消任务的判断 if (self.isCancelled) { return; } for (int i = 0; i < 10000; i ++) { NSLog(@"-----3-----%@-----", [NSThread currentThread]); } // 增加队列是否取消任务的判断 if (self.isCancelled) { return; } for (int i = 0; i < 10000; i ++) { NSLog(@"-----4-----%@-----", [NSThread currentThread]); } } @end
六. NSOperationQueue线程间通信
开启子线程下载图片,下载完成后,在主线程更新UI。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ NSString *urlString = @"http://www.wallcoo.com/animal/Dogs_Summer_and_Winter/wallpapers/1920x1200/DogsB10_Lucy.jpg"; NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; if (data) { UIImage *image = [UIImage imageWithData:data]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"更新UI"); _backImageView.image = image; }]; } }]; }
七. 操作依赖
可以在同一个queue中添加操作依赖,也可以在不痛的队列中添加操作依赖。
注意:千万不要A依赖B,同时B也依赖A。
经典实例:合成图片
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 队列1用于下载图片 NSOperationQueue *queue1 = [[NSOperationQueue alloc] init]; // 队列2用于合成图片 NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; __block UIImage *image1 = nil; __block UIImage *image2 = nil; // 下载图片1 NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSString *urlString = @"http://www.wallcoo.com/animal/Dogs_Summer_and_Winter/wallpapers/1920x1200/DogsB10_Lucy.jpg"; NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; image1 = image; }]; // 下载图片2 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSString *urlString = @"http://img4.duitang.com/uploads/item/201507/30/20150730163204_A24MX.thumb.700_0.jpeg"; NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; image2 = image; }]; [operation1 setCompletionBlock:^{ NSLog(@"图片1下载完成"); }]; [operation2 setCompletionBlock:^{ NSLog(@"图片2下载完成"); }]; // 合成图片 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ UIGraphicsBeginImageContext(CGSizeMake(200, 200)); [image1 drawInRect:CGRectMake(0, 0, 100, 200)]; [image2 drawInRect:CGRectMake(100, 0, 100, 200)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // 主线程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"主线程更新UI"); _backImageView.image = newImage; }]; }]; // 合成图片依赖于下载图片---只有图片下载完成,才可以进行合成操作 [operation3 addDependency:operation1]; [operation3 addDependency:operation2]; // 将下载图片添加到队列1中 [queue1 addOperation:operation1]; [queue1 addOperation:operation2]; // 将合成图片添加到队列2中 [queue2 addOperation:operation3]; }