多线程方案 NSOperation 是基于 GCD 开发的,几乎完全面向对象,因而与 GCD 比较其更加灵活、可扩展、可控以及代码可读性更强。例如:可添加操作之间的依赖关系,方便的控制执行顺序;可设定操作执行的优先级;可以很方便的取消一个操作的执行;可通过 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled等;
NSOperation 初步认识
实际开发中无法直接使用 NSOperation,因为其只是一个抽象基类并不具备封装操作的能力,实际开发中主要使用其两个子类:NSInvocationOperation 和 NSBlockOperation,或者是自定义一个子类; NSOperation 的子类结合 NSOperationQueue 实现多线程即通过操作+队列实现。
- 操作 (NSOperation 子类):执行操作就是线程中执行的那段代码,可以理解为 GCD 中的 Block 块任务,但是又不能等同,因为一个操作中可以 add 多个任务;
- 操作队列 (NSOperationQueue):用来存放操作的队列,操作 add 进相应队列后系统会⾃动将 NSOperationQueue 中的 NSOperation 任务取出,放入新线程中执⾏;
NSOperation 操作执行状态
NSOperation 每一个操作的执行状态过程:isReady → isExecuting → isFinished;这里并不是通过一个 state 属性标识,而是直接由上面那些 keypath 的 KVO 通知决定,也就是说,当一个操作在准备好被执行的时候,它发送了一个 KVO 通知给 isReady 的 keypath,让这个 keypath 对应的属性 isReady 在被访问的时候返回 YES;
- isReady: 返回 YES 表示操作已经准备好被执行, 如果返回 NO 则说明还有其他没有先前的相关步骤没有完成。
- isExecuting: 返回 YES 表示操作正在执行,反之则没在执行。
- isFinished : 返回 YES 表示操作执行成功或者被取消了,NSOperationQueue 只有当它管理的所有操作的 isFinished 属性全标为 YES 以后操作才停止出列,也就是队列停止运行,所以正确实现这个方法对于避免死锁很关键。
NSOperation 取消操作
NSOperation 操作执行之前可以通过 Operation 对象调用 cancel 方法取消某个相关操作,取消原因可能是需求需要或者是某个操作失败,在 GCD 中如果是已经加入到队列的则无法取消了;这里 NSOperation 对象调用 cancel 方法的时候会通过 KVO 通知 isCancelled 的 keypath 来修改 isCancelled 属性的返回值;
NSOperation 操作优先级设置
NSOperation 是可以通过设置其 queuePriority 属性来指定操作的优先级,而 GCD 只有队列才有优先级,也相对更加灵活;但是这里的优先级,只是会让 CPU 有更高的几率调用先, 并不是说设置高就一定全部先完成再执行后面的。
- NSOperationQueuePriorityVeryHigh
- NSOperationQueuePriorityHigh
- NSOperationQueuePriorityNormal
- NSOperationQueuePriorityLow
- NSOperationQueuePriorityVeryLow
NSOperation 操作设置依赖
NSOperation 可以设置多个操作之间的依赖关系,等 A 操作执行完再执行 B 操作这样,GCD 中如果要实现这种场景只用通过复杂的 dispatch_barrier_async
分割任务执行顺序实现或者是通过 dispatch_semaphore 信号量实现;
NSOperation 操作完成的 Block 回调
NSOperation 提供了一个操作完成的 completionBlock 属性,可以更加方便监听操作完成时机;
1 2 3
| operation.completionBlock = ^{ };
|
NSOperation 子类配合 NSOperationQueue 实现多线程
Operation 操作单独使用的话,也就是手动调用 start 方法的话操作默认是在当前线程执行不会开启新线程;配合 NSOperationQueue 使用将操作添加进队列后,系统会执行操作,并且是在新线程中。当然如果是 [NSOperationQueue mainQueue]
获取主队列,在主队列中执行的话就不会开启新线程了,只是回到主线程执行。
NSOperation 实现多线程的使用步骤:
- 创建操作:是一个 NSOperation 子类对象创建过程,并且添加任务到操作中;
- 创建队列:创建 NSOperationQueue 对象过程;
- 操作加入队列:将创建好的 NSOperation 子类对象添加入已创建队列中;
- 操作完成监听:调用 completionBlock 属性监听操作完成回调;
NSInvocationOperation 实现示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| - (void)InvocationOperation { NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(executeOperation:) object:@"zeroccOperation"]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation]; operation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ }]; NSLog(@"NSInvocationOperation 任务执行完成"); };
}
- (void)executeOperation:(id)object { NSLog(@" NSInvocationOperation 执行任务---%@:%@",object,[NSThread currentThread]); }
|
1 2 3
| ... CCBlogCode[24149:4544608] NSInvocationOperation 执行任务---zeroccOperation:<NSThread: 0x600003b24680>{number = 3, name = (null)} ... CCBlogCode[24149:4544609] NSInvocationOperation 任务执行完成
|
NSBlockOperation 实现示例
NSBlockOperation 创建的 operation 操作,可以通过调用 addExecutionBlock:(void (^)(void))block
方法往操作中添加多个任务,并且具备开启新线程的能力;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| - (void)BlockOperation { NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"任务1---%@",[NSThread currentThread]); }]; [operation cancel]; [operation addExecutionBlock:^{ NSLog(@"任务2---%@",[NSThread currentThread]); }];
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation]; operation.completionBlock = ^{ NSLog(@"NSBlockOperation 任务执行完成"); }; }
|
1 2 3 4
| ... CCBlogCode[24203:4547036] 任务1---<NSThread: 0x600000928780>{number = 4, name = (null)} ... CCBlogCode[24203:4547037] 任务2---<NSThread: 0x60000092c5c0>{number = 5, name = (null)} ... CCBlogCode[24203:4547037] NSBlockOperation 任务执行完成
|
NSOperationQueue 队列常用姿势
队列的类型
主队列:添加到主队列中的操作都会放到主线程中执行。主队列的获取方式:
1
| NSOperationQueue *queue = [NSOperationQueue mainQueue];
|
非主队列:添加到非主队列的操作都会放到子线程中并发执行,等同于并发队列。并行队列创建方式:
1
| NSOperationQueue *queue = [[NSOperationQueue alloc] init];
|
队列中添加操作 Operation
往队列中添加操作的两种方式:
1 2
| - (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block
|
示例demo:
1 2 3 4 5 6 7
| [queue addOperation:operation];
[queue addOperationWithBlock:^{ }];
|
队列设置最大并发数
通过设置队列 maxConcurrentOperationCount 这个属性来控制一个特定队列中可以有多少个操作同时参与并发执行。设置为1时,是不是就是串行队列呢?(本人认为不是)。maxConcurrentOperationCount = 1 -> serial queue。这里是虽然是队列的属性,但是控制的不是并发线程的数量,控制的是Operation操作数量,他控制的只是 NSOperation 对象调用多少个操作能同时执行的情况。
先理理并发数这个概念,并发执行过程不一定就开启了新的线程,这里涉及系统线程池对线程的管理,但是如果是串行队列是一定不会开启新线程的。那么看看如下 demo 执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| - (void)OperationQueueMaxConcurrentOperationCount{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperationCount = 1;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"任务1---%@",[NSThread currentThread]); }]; [operation1 addExecutionBlock:^{ NSLog(@"任务2---%@",[NSThread currentThread]); }]; [operation1 addExecutionBlock:^{ NSLog(@"任务3---%@",[NSThread currentThread]); }]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation2---%@",[NSThread currentThread]); }]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation3---%@",[NSThread currentThread]); }]; [queue addOperation:operation1]; [queue addOperation:operation2]; [queue addOperation:operation3]; }
|
1 2 3 4 5 6 7 8
|
... CCBlogCode[29007:4865646] 任务2---<NSThread: 0x6000013884c0>{number = 4, name = (null)} ... CCBlogCode[29007:4865645] 任务3---<NSThread: 0x60000138d000>{number = 5, name = (null)} ... CCBlogCode[29007:4865647] 任务1---<NSThread: 0x60000138c840>{number = 3, name = (null)} ... CCBlogCode[29007:4865645] operation2---<NSThread: 0x60000138d000>{number = 5, name = (null)} ... CCBlogCode[29007:4865647] operation3---<NSThread: 0x60000138c840>{number = 3, name = (null)}
|
队列管理操作 operation 的执行状态
1 2 3 4 5 6 7
| [queue cancelAllOperations];
[queue setSuspended:YES];
[queue setSuspended:NO];
|
线程间通信和操作依赖设置
线程间通信:子线程中操作执行完毕回到主线程做相关数据同步或者UI相关;只需在执行任务完成的相应地方获取主队列,并往主队列中添加操作;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| - (void)OperationBackMainThread { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:3]; NSLog(@"任务1---%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"刷新 UI---%@",[NSThread currentThread]); }]; }]; }
|
1 2 3
| ... CCBlogCode[29820:4891621] 任务1---<NSThread: 0x600000c523c0>{number = 3, name = (null)} ... CCBlogCode[29820:4891557] 刷新 UI---<NSThread: 0x600000c0a840>{number = 1, name = main}
|
操作依赖设置:NSOperation 子类实现多线程多个操作之间可以设置依赖关系,等 A 操作执行完再执行 B 操作这样,GCD 中如果要实现这种场景只用通过复杂的 dispatch_barrier_async
分割任务执行顺序实现或者是通过 dispatch_semaphore 信号量实现;但是绝对不能互相依赖,A依赖B,B又依赖A, 不然的话就死锁了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| - (void)OperationAddDependency { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:3]; NSLog(@"operation1---%@",[NSThread currentThread]); }]; NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:1]; NSLog(@"operation2---%@",[NSThread currentThread]); }]; NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"operation3---%@",[NSThread currentThread]); }]; [operation2 addDependency:operation1]; [operation3 addDependency:operation2]; [queue addOperation:operation1]; [queue addOperation:operation2]; [queue addOperation:operation3]; }
|
1 2 3 4
| ... CCBlogCode[29460:4881327] operation1---<NSThread: 0x6000003a7d80>{number = 3, name = (null)} ... CCBlogCode[29460:4881260] operation2---<NSThread: 0x6000003ab7c0>{number = 4, name = (null)} ... CCBlogCode[29460:4881327] operation3---<NSThread: 0x6000003a7d80>{number = 3, name = (null)}
|
本文标题:iOS 开发之多线程 NSOperation
文章作者:zerocc
发布时间:2015年08月06日
原始链接:http://www.zerocc.com.cn/20150806.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!