iOS多线程--NSOperation
NSOperation是基于GCD的一套多线程实现方案,和GCD一样,线程的生命周期是由系统来自动管理的,不用像NSThread和Pthread一样让程序员手动管理。相对于GCD来说,它更加地面向对象,并且比GCD多了一些更加简单实用的功能,另外,由于它的API是纯OC的,深受广大程序员喜爱,实用频率很高。
NSOperation主要和NSOperationQueue配合使用实现多线程,一般步骤如下:
1.先将需要执行的操作封装到一个NSOperation对象中;
2.然后将NSOperation对象添加到NSOperationQueue中;
3.系统会自动将NSOperationQueue中的NSOperation取出来;
4.将取出的NSOperation放到一条线程中执行。
注意:NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类。使用NSOperation子类的方式有如下三种:
1.NSInvocationOperation
2.NSBlockOperation
3.自定义子类继承NSOperation,实现内部相应的方法。
我们首先来看前两种:
一、NSOperation:
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 [self invocationOperation]; 4 } 5 6 -(void)invocationOperation 7 { 8 9 // 1.创建操作对象,封住需要执行的任务 10 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil]; 11 12 // 2.执行操作(默认情况下,如果操作没有放到操作队列中,同步执行) 13 [operation start]; 14 } 15 16 -(void)download1 17 { 18 NSLog(@"下载1----%@",[NSThread currentThread]); 19 }
结果如下:
可以看出,默认情况下,如果操作没有放到操作队列中,会在主线程同步执行,只有讲NSOperation放到NSOperationQueue中才会异步执行。
二、NSBlockOperation
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 [self blockOperation]; 4 } 5 6 - (void)blockOperation 7 { 8 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 9 NSLog(@"NSBlockOperation--下载1---%@",[NSThread currentThread]); 10 }]; 11 12 [operation addExecutionBlock:^{ 13 NSLog(@"NSBlockOperation--下载2---%@",[NSThread currentThread]); 14 }]; 15 16 [operation addExecutionBlock:^{ 17 NSLog(@"NSBlockOperation--下载3---%@",[NSThread currentThread]); 18 }]; 19 [operation start]; 20 }
结果如图:
我们会发现,NSBlockOperation当只有单个任务的时候,也是默认在主线程执行,当任务数大于1的时候,会开启子线程并发执行其它的操作
三、配合使用NSOperationQueue
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 [self operationQueue]; 4 } 5 6 - (void)operationQueue 7 { 8 NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil]; 9 NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil]; 10 NSInvocationOperation *operation3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil]; 11 NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{ 12 NSLog(@"NSBlockOperation--1---%@",[NSThread currentThread]); 13 }]; 14 [operation4 addExecutionBlock:^{ 15 NSLog(@"NSBlockOperation--2---%@",[NSThread currentThread]); 16 }]; 17 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 18 [queue addOperation:operation1]; 19 [queue addOperation:operation2]; 20 [queue addOperation:operation3]; 21 [queue addOperation:operation4]; 22 } 23 -(void)download1 24 { 25 NSLog(@"下载1----%@",[NSThread currentThread]); 26 } 27 -(void)download2 28 { 29 NSLog(@"下载2----%@",[NSThread currentThread]); 30 } 31 -(void)download3 32 { 33 NSLog(@"下载3----%@",[NSThread currentThread]); 34 }
结果如下
可以看出,只要是添加到NSOperationQueue中的操作,系统都会自动为我们开启子线程来执行,并且是并发无序的执行,和添加的顺序无关。
然而有些时候我们需要明确地指定操作的顺序,我们可以在NSOperation之间设置依赖来保证执行顺序。
比如操作1要在操作2后面执行,可以这么写:[operation1 addDependency:operation2] 意思是operation1依赖于operation2,也就是operation1要在operation2后面执行
上代码验证下:
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 [self operationQueue]; 4 } 5 6 - (void)operationQueue 7 { 8 NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil]; 9 NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil]; 10 NSInvocationOperation *operation3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil]; 11 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 12 [operation3 addDependency:operation1]; 13 [operation1 addDependency:operation2]; 14 [queue addOperation:operation1]; 15 [queue addOperation:operation2]; 16 [queue addOperation:operation3]; 17 } 18 -(void)download1 19 { 20 NSLog(@"下载1----%@",[NSThread currentThread]); 21 } 22 -(void)download2 23 { 24 NSLog(@"下载2----%@",[NSThread currentThread]); 25 } 26 -(void)download3 27 { 28 NSLog(@"下载3----%@",[NSThread currentThread]); 29 }
结果如下:
操作顺序Operation2->Operation1->Operation3,完全正确,其实就是谁依赖谁,谁就在它的后面执行
另外也可以在不同queue的NSOperation之间设置依赖
操作顺序:4->3->2->1 其余两个并发执行
下面列出了NSOperationQueue的一些属性和方法:
1.- (void)cancelAllOperations 取消队列的所有操作。PS:也可以调用NSOperation的-(void)cancel方法取消单个操作
2.@property (getter=isSuspended) BOOL suspended;YES表示暂停队列,NO表示回复队列
3.@property NSInteger maxConcurrentOperationCount;表示最大并发数,一般不大于5
四、自定义NSOperation
当将自定义的NSOperation添加到NSOperationQueue的时候,系统会调用NSOperation的-(void)main方法,所以我们只要重写这个方法实现相应的实现即可。
1 -(void)main 2 { 3 @autoreleasepool { // 异步线程无法访问主线程的自动释放池,需要自己添加 4 if (self.isCancelled) return;// 时刻监听操作是否取消,若取消就返回 5 NSURL *url = [NSURL URLWithString:self.matchUrl]; 6 if (self.isCancelled) return; 7 NSData *data = [NSData dataWithContentsOfURL:url]; 8 if (self.isCancelled) return; 9 UIImage *image = [UIImage imageWithData:data]; 10 if ([_delegate respondsToSelector:@selector(operation:finshedDownloadImage:)]) { 11 dispatch_async(dispatch_get_main_queue(), ^{// 在主线程更新UI 12 [self.delegate operation:self finshedDownloadImage:image]; 13 }); 14 } 15 } 16 }
注意由于子线称无法访问主线程的自动释放池,所以需要自己添加。
下面是个小Demo截图,自定义NSOperation实现图片的异步下载,以及避免重复下载同一张图片