我们可以使用NSOperation和NSOperationQueue实现多线程开发,NSOperationQueue的概念类似线程池,相比较NSThread,NSOperation提供了面向对象的语法。
使用NSOperation的过程是创建一个operation
NSOperation通常不会直接拿来使用,我们使用它的子类NSInvocationOperation或者NSBlockOperation,又或者自定义NSOperation的子类,下面分别介绍这三种方式。
NSInvocationOperation
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadOperation) object:nil]; //如果直接使用start,operation会马上运行在主线程,我们基本不会这样使用 //[operation start]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //将operation加入到operationqueue后,operationqueue会自动执行start方法,且运行在多线程中 [queue addOperation:operation]; }
关于NSOperationQueue我们还可以设置它的相关属性
queue.maxConcurrentOperationCount 获取或者设置最大的并发运行数,默认值为-1,表示不限制同时运行的线程数
queue.name 设置线程池名称
[NSOperationQueue currentQueue] 获取当前线程池
[NSOperationQueue mainQueue] 获取主线程池
[queue cancelAllOperations] 对线程池中的线程执行cancel方法设置取消标记,
NSBlockOperation
NSBlockOpration的使用流程基本与NSInvocationOperation一致,只是使用了block代替selector
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. _imageview = [[UIImageView alloc] initWithFrame:CGRectMake(40, 40, 100, 80)]; [self.view addSubview:_imageview]; NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%hhd",[NSThread isMainThread]); NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.iyi8.com/uploadfile/2014/1002/20141002112243630.jpg"]]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"%hhd",[NSThread isMainThread]); [_imageview setImage:[UIImage imageWithData:data]]; }]; }]; //为operation添加执行完毕的block operation.completionBlock = ^{ NSLog(@"operation done"); }; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation]; }
上面代码在多线程中网络请求图片,并在主线程中对ui进行更新,NSBlockOperation比NSInvocationOperation更加方便常用
自定义NSOperation子类
上面我们直接使用了框架中提供的NSOperation子类,我们还可以自己定义NSoperation子类
自定义Operation的步骤如下
1 继承NSOperation
2 实现main函数
3 在main函数中创建自动释放池
4 把任务代码写在自动释放池中
下面我们自定义一个NSOperation子类,用来下载图片
ZLTOperation.h #import <Foundation/Foundation.h> @protocol DownImageProtocal; @interface ZLTOperation : NSOperation -(id)initWithImageUrl:(NSURL *)url delagete:(id<DownImageProtocal>) delegate; @end @protocol DownImageProtocal <NSObject> -(void)updateViewWithImage:(UIImage *)image; @end ZLTOperation.m #import "ZLTOperation.h" @interface ZLTOperation() { NSURL *_imageUrl; id<DownImageProtocal> _delegate; } @end @implementation ZLTOperation -(id)initWithImageUrl:(NSURL *)url delagete:(id<DownImageProtocal>) delegate { self = [super init]; if (self) { _imageUrl = url; _delegate = delegate; } return self; } - (void)main { @autoreleasepool { //任务是否被取消 if ([self isCancelled]) { return; } NSData *data = [NSData dataWithContentsOfURL:_imageUrl]; //任务是否被取消 if ([self isCancelled]) { return; } if ([_delegate respondsToSelector:@selector(updateViewWithImage:)]) { [(NSObject *)_delegate performSelectorOnMainThread:@selector(updateViewWithImage:) withObject:[UIImage imageWithData:data] waitUntilDone:NO]; } } } @end
测试
ZViewController.h @interface ZViewController : UIViewController<DownImageProtocal> @end ZViewController.m @interface ZViewController () { UIImageView *_imageview; } @end @implementation ZViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. _imageview = [[UIImageView alloc] initWithFrame:CGRectMake(40, 40, 100, 80)]; [self.view addSubview:_imageview]; ZLTOperation *operation = [[ZLTOperation alloc] initWithImageUrl:[NSURL URLWithString:@"http://www.iyi8.com/uploadfile/2014/1002/20141002112243630.jpg"] delagete:self]; //为operation添加执行完毕的block operation.completionBlock = ^{ NSLog(@"operation done"); }; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation]; } - (void)updateViewWithImage:(UIImage *)image { _imageview.image = image; } @end
NSOperation其他常用方法和属性
- (BOOL)isCancelled;
- (void)cancel; 取消任务
- (void)setCompletionBlock:(void (^)(void))block 添加任务完成block
- (double)threadPriority
- (void)setThreadPriority:(double)p 任务优先级
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
- (NSArray *)dependencies; 添加依赖
依赖用来控制线程执行顺序,如果A添加B为他的依赖,那么只有当操作B完成后才会执行操作A,操作依赖关系可以设置多个,例如A依赖于B、B依赖于C…但是千万不要设置为循环依赖关系(例如A依赖于B,B依赖于C,C又依赖于A),否则是不会被执行的。
并发的NSOperation
前面提过直接执行operation的start方法时,operation会运行在主线程,这是因为NSOperation默认是非并发的,我们也可以自己实现并发的operation类,这需要重载一系列方法,创建并发operation的一般步骤如下
- 你需要创建一个
subclass
- 除了重载
main
方法,实现并发你还需要至少重载start
,isConcurrent
,isExecuting
,isFinished
四个方法 - 在
start
里,创建Thread
或者调用一个异步函数 - 更新
isExecuting
,并且发送相应KVO消息 - 任务结束后,你还得更新
isExecuting
和isFinished
,发送相应KVO消息
一般我们不需要创建并发的NSOperation,配合NSOperationQueue来调用,而不是自己调用start