NSOperation和NSOperationQueue
基本概念
NSOperation
和NSOperationQueue
实现多线程的步骤- 首先,将需要
执行的任务
封装到一个NSOperation对象中 - 然后,将NSOperation对象添加到NSOperationQueue中
- 系统会
自动
将NSOperationQueue中的NSOperation取出来放到一条新线程
中执行
- 首先,将需要
- NSOperation是个
抽象类
,并不具备封装任务的能力,必须
通过它的子类
来封装任务NSInvocationOperation
NSBlockOperation
自定义
继承自NSOperation的操作,实现内部相应的方法
NSOperation
NSInvocation
Operation的使用
- 创建NSInvocationOperation对象
// 对象方法 - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
必须
调用start
方法才开始执行操作// 一旦执行操作,就会调用target的sel方法 - (void)start;
默认
情况下,调用了start方法后并不会
开一条新线程去执行操作,而是在当前线程同步执行
操作只有
将NSOperation放到一个NSOperationQueue中,才会自动异步
执行操作示例
- (void)invocationOperation { NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; // 不会开新线程执行任务 [op start]; } - (void)run { NSLog(@"------%@", [NSThread currentThread]); }
NSBlock
Operation
- 创建NSBlockOperation对象
// 类方法 + (id)blockOperationWithBlock:(void (^)(void))block;
- 通过
addExecutionBlock:
方法添加更多
的操作- (void)addExecutionBlock:(void (^)(void))block;
只有
当NSBlockOperation封装的任务数 > 1
,才会异步
执行操作示例
- (void)blockOperation { // 只要NSBlockOperation封装的操作数 > 1,就会【异步】执行操作 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 这个任务是在【主线程】 NSLog(@"下载1------%@", [NSThread currentThread]); }]; // 添加额外的任务(在【子线程】执行) [op addExecutionBlock:^{ NSLog(@"下载2------%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"下载3------%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"下载4------%@", [NSThread currentThread]); }]; [op start]; }
自定义
继承自NSOperation的操作
- 重写
-(void)main
方法,在里面实现想执行的任务逻辑 - 重写
-(void)main
方法的注意点
创建自动释放池
(因为如果是异步操作,无法访问主线程的自动释放池)- 在合适的地方通过
-(BOOL)isCancelled
方法检测操作是否被取消,对取消做出响应
示例
/** * 在这个方法里定义需要执行的任务逻辑 */ -(void)main { //【创建自动释放池】,在大括号结束后这段代码产生的消耗就都被释放了 @autoreleasepool { for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]); } // 由于任务一旦开始执行就没办法停止下来 // 苹果官方建议,如果是自定义的Operation,而且内部的执行逻辑很耗时 // 如果外面调用了cancel方法,可以通过在一段耗时逻辑后尝试调用一次isCancelled方法来判断操作是否已经取消,以用来中断任务的执行 // 注意必须合理布局此isCancelled方法 if (self.isCancelled) return; for (NSInteger i = 0; i< 1000; i++) { NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]); } if (self.isCancelled) return; for (NSInteger i = 0; i < 1000; i++) { NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]); } if (self.isCancelled) return; } }
NSOperation设置依赖
关系
- 比如一定要让操作A执行完后,才能执行操作B,可以这么写
// 操作B依赖于操作A [operationB addDependency:operationA];
可以
在不同队列
的NSOperation之间设置依赖关系- NSOperation之间
不能相互依赖
,比如:A依赖B,B依赖A 示例
- (void)addDependencyTest { // 直接创建 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download1----%@", [NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download2----%@", [NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download3----%@", [NSThread currentThread]); }]; // 设置依赖后再添加到队列 // 当op1和op2都执行完才执行op3,但是op1和op2谁先执行完不确定 [op3 addDependency:op1]; [op3 addDependency:op2]; // 【不能】相互依赖 // [op3 addDependency:op1]; // [op1 addDependency:op3]; [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; }
NSOperation执行完毕后
想做一些事情,可以通过下面的block设置
- completionBlock
-(void (^)(void))completionBlock; -(void)setCompletionBlock:(void (^)(void))block;
示例
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download----%@", [NSThread currentThread]); }]; // 任务执行完毕就会执行这个block op.completionBlock = ^{ // 也是在子线程中执行 NSLog(@"op执行完毕后执行---%@", [NSThread currentThread]); };
NSOperationQueue
- NSOperationQueue的作用
- 上面已经讨论过NSOperation是通过调用
start
方法来执行任务,但默认
是同步
执行的 - 如果将NSOperation添加到NSOperationQueue中,系统会
自动异步
执行NSOperation中的操作,实质
上NSOperationQueue内部也是自动调用了NSOperation的start
方法来执行任务 - 这种队列
同时
包含了串行
、并发
功能
- 上面已经讨论过NSOperation是通过调用
添加操作到NSOperationQueue中方法
-(void)addOperation:(NSOperation *)op; // 个人更喜欢这种方式添加任务 -(void)addOperationWithBlock:(void (^)(void))block;
最大并发数:
maxConcurrentOperationCount
- 定义:同时执行的任务数
当最大并发数为1的时候就是串行队列
,但所有任务不一定在同一个线程上执行完,可能在多个线程上执行,但是一定是按顺序执行
最大并发数设置
-(NSInteger)maxConcurrentOperationCount; -(void)setMaxConcurrentOperationCount:(NSInteger)cnt; // 调用示例 self.queue.maxConcurrentOperationCount = 1;
暂停和恢复队列:
suspended
暂停后
还未执行
的任务将不会被执行,但是已经开始
的任务是不能
停下来了// YES代表暂停(挂起)队列,NO代表恢复队列 -(void)setSuspended:(BOOL)b; -(BOOL)isSuspended; // 调用示例 self.queue.suspended = !self.queue.suspended;
suspended的应用场景:
提高用户体验
- 场景:队列正在执行一些耗时操作,当用户执行滚动操作时,为了用户体验可以调用这个属性先让队列中的任务暂停执行,以用来提高用户体验
- 解惑:虽然队列中的任务在子线程中执行,但是由于队列中的任务很耗,所以会影响资源的抢夺,导致一些不好的用户体验
取消队列的所有操作:
cancelAllOperations
- 取消队列中的所有任务,一旦取消就会将队列中任务全部移除
- 这个方法内部其实是调用了NSOperation中的cancel方法
- 所以可以通过NSOperation中的cancel方法来取消
单个
操作 只能
取消未执行
的任务,已经开始
的任务不能取消
,除非任务中主动判断了是否取消操作queue.isCancelled来中断操作-(void)cancelAllOperations; // 调用示例 [queue cancelAllOperations];
GCD的队列类型和NSOperationQueue队列比较
GCD的队列类型
并发
队列- 自己创建的(类型:DISPATCH_QUEUE_CONCURRENT)
- 全局队列
串行
队列- 自己创建的(类型:DISPATCH_QUEUE_SERIAL or NULL)
- 主队列(特殊的串行队列)
NSOperationQueue队列类型
- 主队列
- [NSOperationQueue mainQueue]
- 凡是添加到主队列中的任务(NSOperation),都会放到
主线程
中执行
- 非主队列(其他队列)
- 创建:[[NSOperationQueue alloc] init]
同时
包含了【串行】、【并发】功能- 当设置最大并发数为
queue.maxConcurrentOperationCount = 1
时就是串行队列
- 添加到这种队列中的任务(NSOperation),会
自动
放到子线程
中执行
线程间通信
-(void)test
{
[[[NSOperationQueue alloc] init] addOperationWithBlock:^
{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
UIImage *image = [UIImage imageWithData:data];
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
self.imageView.image = image;
}];
}];
}