前言
1.上一讲简介了NSThread的使用。尽管也能够实现多线程编程,可是须要我们去管理线程的生命周期。还要考虑线程同步、加锁问题,造成一些性能上的开销。我们也能够配合使用NSOperation和NSOperationQueue实现多线程编程,实现步骤大致是这种:
1> 先将须要运行的操作封装到一个NSOperation对象中
2> 然后将NSOperation对象加入到NSOperationQueue中
3> 系统会自己主动将NSOperation中封装的操作放到一条新线程中运行
在此过程中。我们根本不用考虑线程的生命周期、同步、加锁等问题
以下列举一个应用场景。比方微博的粉丝列表:
每一行的头像肯定要从新浪server下载图片后才干显示的,并且是须要异步下载。这时候你就能够把每一行的图片下载操作封装到一个NSOperation对象中,上面有6行。所以要创建6个NSOperation对象,然后加入到NSOperationQueue中,分别下载不同的图片。完成下载后。回到相应的行将图片显示出来。
2.默认情况下。NSOperation并不具备封装操作的能力,必须使用它的子类。使用NSOperation子类的方式有3种:
1> NSInvocationOperation
2> NSBlockOperation
3> 自己定义子类继承NSOperation,实现内部对应的方法
这讲先介绍怎样用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。
一、NSInvocationOperation
1 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease]; 2 [operation start];
* 第1行初始化了一个NSInvocationOperation对象,它是基于一个对象和selector来创建操作
* 第2行调用了start方法,紧接着会立即运行封装好的操作。也就是会调用self的run:方法,而且将@"mj"作为方法參数
* 这里要注意:默认情况下,调用了start方法后并不会开一条新线程去运行操作。而是在当前线程同步运行操作。
仅仅有将operation放到一个NSOperationQueue中,才会异步运行操作。
二、NSBlockOperation
1.同步运行一个操作
1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ 2 NSLog(@"运行了一个新的操作"); 3 }]; 4 // 開始运行任务 5 [operation start];
* 第1行初始化了一个NSBlockOperation对象,它是用一个Block来封装须要运行的操作
* 第2行调用了start方法。紧接着会立即运行Block中的内容
* 这里还是在当前线程同步运行操作,并没有异步运行
2.并发运行多个操作
1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ 2 NSLog(@"运行第1次操作,线程:%@", [NSThread currentThread]); 3 }]; 4 5 [operation addExecutionBlock:^() { 6 NSLog(@"又运行了1个新的操作,线程:%@", [NSThread currentThread]); 7 }]; 8 9 [operation addExecutionBlock:^() { 10 NSLog(@"又运行了1个新的操作,线程:%@", [NSThread currentThread]); 11 }]; 12 13 [operation addExecutionBlock:^() { 14 NSLog(@"又运行了1个新的操作。线程:%@", [NSThread currentThread]); 15 }]; 16 17 // 開始运行任务 18 [operation start];
* 第1行初始化了一个NSBlockOperation对象
* 分别在第5、9、13行通过addExecutionBlock:方法加入了新的操作,包含第1行的操作,一共封装了4个操作
* 在第18行调用start方法后,就会并发地运行这4个操作,也就是会在不同线程中运行
1 2013-02-02 21:38:46.102 thread[4602:c07] 又运行了1个新的操作,线程:<NSThread: 0x7121d50>{name = (null), num = 1} 2 2013-02-02 21:38:46.102 thread[4602:3f03] 又运行了1个新的操作,线程:<NSThread: 0x742e1d0>{name = (null), num = 5} 3 2013-02-02 21:38:46.102 thread[4602:1b03] 运行第1次操作,线程:<NSThread: 0x742de50>{name = (null), num = 3} 4 2013-02-02 21:38:46.102 thread[4602:1303] 又运行了1个新的操作,线程:<NSThread: 0x7157bf0>{name = (null), num = 4}
能够看出,每一个操作所在线程的num值都不一样,说明是不同线程
三、NSOperation的其它使用方法
1.取消操作
operation開始运行之后, 默认会一直运行操作直到完毕,我们也能够调用cancel方法中途取消操作
[operation cancel];
2.在操作完毕后做一些事情
假设想在一个NSOperation运行完成后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情
operation.completionBlock = ^() { NSLog(@"运行完成"); };
当operation封装的操作运行完成后,就会回调Block里面的内容
四、自己定义NSOperation
假设NSInvocationOperation和NSBlockOperation不能满足需求,我们能够直接新建子类继承NSOperation,并加入不论什么须要运行的操作。
假设仅仅是简单地自己定义NSOperation。仅仅须要重载-(void)main这种方法。在这种方法里面加入须要运行的操作。
以下写个子类DownloadOperation来下载图片
1.继承NSOperation。重写main方法
DownloadOperation.h
#import <Foundation/Foundation.h> @protocol DownloadOperationDelegate; @interface DownloadOperation : NSOperation // 图片的url路径 @property (nonatomic, copy) NSString *imageUrl; // 代理 @property (nonatomic, assign) id<DownloadOperationDelegate> delegate; - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate; @end // 图片下载的协议 @protocol DownloadOperationDelegate <NSObject> - (void)downloadFinishWithImage:(UIImage *)image; @end
DownloadOperation.m
1 #import "DownloadOperation.h" 2 3 @implementation DownloadOperation 4 @synthesize delegate = _delegate; 5 @synthesize imageUrl = _imageUrl; 6 7 // 初始化 8 - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate { 9 if (self = [super init]) { 10 self.imageUrl = url; 11 self.delegate = delegate; 12 } 13 return self; 14 } 15 // 释放内存 16 - (void)dealloc { 17 [super dealloc]; 18 [_imageUrl release]; 19 } 20 21 // 运行主任务 22 - (void)main { 23 // 新建一个自己主动释放池。假设是异步运行操作,那么将无法訪问到主线程的自己主动释放池 24 @autoreleasepool { 25 // .... 26 } 27 } 28 @end
* 在第22行重载了main方法。等会就把下载图片的代码写到这种方法中
* 假设这个DownloadOperation是在异步线程中运行操作,也就是说main方法在异步线程调用,那么将无法訪问主线程的自己主动释放池。所以在第24行创建了一个属于当前线程的自己主动释放池
2.正确响应取消事件
* 默认情况下。一个NSOperation開始运行之后。会一直运行任务到结束。就比方上面的DownloadOperation,默认会运行完main方法中的全部代码。
* NSOperation提供了一个cancel方法。能够取消当前的操作。
* 假设是自己定义NSOperation的话,须要手动处理这个取消事件。比方,一旦调用了cancel方法。应该立即终止main方法的运行。并及时回收一些资源。
* 处理取消事件的详细做法是:在main方法中定期地调用isCancelled方法检測操作是否已经被取消,也就是说是否调用了cancel方法,假设返回YES,表示已取消,则马上让main方法返回。
* 下面地方可能须要调用isCancelled方法:
- 在运行不论什么实际的工作之前,也就是在main方法的开头。由于取消可能发生在不论什么时候,甚至在operation运行之前。
- 运行了一段耗时的操作之后也须要检測操作是否已经被取消
1 - (void)main { 2 // 新建一个自己主动释放池,假设是异步运行操作,那么将无法訪问到主线程的自己主动释放池 3 @autoreleasepool { 4 if (self.isCancelled) return; 5 6 // 获取图片数据 7 NSURL *url = [NSURL URLWithString:self.imageUrl]; 8 NSData *imageData = [NSData dataWithContentsOfURL:url]; 9 10 if (self.isCancelled) { 11 url = nil; 12 imageData = nil; 13 return; 14 } 15 16 // 初始化图片 17 UIImage *image = [UIImage imageWithData:imageData]; 18 19 if (self.isCancelled) { 20 image = nil; 21 return; 22 } 23 24 if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) { 25 // 把图片数据传回到主线程 26 [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO]; 27 } 28 } 29 }
* 在第4行main方法的开头就先推断operation有没有被取消。
假设被取消了,那就没有必要往下运行了
* 经过第8行下载图片后,在第10行也须要推断操作有没有被取消
* 总之,运行了一段比較耗时的操作之后。都须要推断操作有没有被取消
* 图片完成下载后。在第26行将图片数据传递给了代理(delegate)对象