NSOperation和NSOperationQueue
基本概念
NSOperation和NSOperationQueue实现多线程的步骤- 首先,将需要
执行的任务封装到一个NSOperation对象中 - 然后,将NSOperation对象添加到NSOperationQueue中
- 系统会
自动将NSOperationQueue中的NSOperation取出来放到一条新线程中执行
- 首先,将需要
- NSOperation是个
抽象类,并不具备封装任务的能力,必须通过它的子类来封装任务NSInvocationOperationNSBlockOperation自定义继承自NSOperation的操作,实现内部相应的方法
NSOperation
NSInvocationOperation的使用
- 创建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]); }
NSBlockOperation
- 创建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;
}];
}];
}