iOS多线程之NSOperation

NSOperation简介

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。
1. NSOperation及其子类
【iOS多线程之NSOperation】NSOperation是个抽象类,并不能封装任务。我们只有使用它的子类来封装任务。
  • NSInvocationOperation
  • NSBlockOperation
  • 自定义NSOperation
1.1 NSInvocationOperation
- (void)invocationOperationTest { NSLog(@"InvocationOperationCurrentThread:%@", [NSThread currentThread]); // 打印当前线程 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"InvocationOperation"}]; [operation start]; }- (void)taskTest:(NSDictionary *)dict { NSString *name = [dict valueForKey:@"name"]; for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"%@---%@", name,[NSThread currentThread]); // 打印当前线程 } } //输出结果 2018-05-24 17:14:46.893 MultiThreadingDemo[3979:594628] InvocationOperationCurrentThread:{number = 1, name = main} 2018-05-24 17:14:48.895 MultiThreadingDemo[3979:594628] InvocationOperation---{number = 1, name = main} 2018-05-24 17:14:50.897 MultiThreadingDemo[3979:594628] InvocationOperation---{number = 1, name = main}

从结果来看,NSInvocationOperation是在主线程中执行的,并没有开辟新线程,那是不是说明NSInvocationOperation一定在主线程执行呢?
show the code测试一下
- (void)invocationInOtherThread { NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(invocationOperationTest) object:nil]; thread1.name=@"invocationThread"; [thread1 start]; } //输出结果 2018-05-24 17:23:33.505 MultiThreadingDemo[3979:608510] InvocationOperationCurrentThread:{number = 3, name = invocationThread} 2018-05-24 17:23:35.511 MultiThreadingDemo[3979:608510] InvocationOperation---{number = 3, name = invocationThread} 2018-05-24 17:23:37.514 MultiThreadingDemo[3979:608510] InvocationOperation---{number = 3, name = invocationThread}

结果证明我们之前的推测是错误的,NSInvocationOperation在我们新开辟的线程中执行。
结论:单独使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程(非主线程)执行的,并没有开启新线程。
1.2 NSBlockOperation 首先看下API提供的方法:
//类方法 + (instancetype)blockOperationWithBlock:(void (^)(void))block; //实例方法 - (void)addExecutionBlock:(void (^)(void))block; //封装的任务 @property (readonly, copy) NSArray *executionBlocks;

上代码
- (void)blockOperationTest { NSLog(@"BlockOperationCurrentThread:%@", [NSThread currentThread]); // 打印当前线程 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ [self taskTest:@{@"name":@"BlockOperation1"}]; }]; [operation addExecutionBlock:^{ [self taskTest:@{@"name":@"BlockOperation2"}]; }]; [operation addExecutionBlock:^{ [self taskTest:@{@"name":@"BlockOperation3"}]; }]; [operation start]; } //输出结果 2018-05-24 17:29:15.550 MultiThreadingDemo[4000:617102] BlockOperationCurrentThread:{number = 1, name = main} 2018-05-24 17:29:17.553 MultiThreadingDemo[4000:617102] BlockOperation1---{number = 1, name = main} 2018-05-24 17:29:17.580 MultiThreadingDemo[4000:617158] BlockOperation2---{number = 3, name = (null)} 2018-05-24 17:29:17.580 MultiThreadingDemo[4000:617157] BlockOperation3---{number = 4, name = (null)} 2018-05-24 17:29:19.554 MultiThreadingDemo[4000:617102] BlockOperation1---{number = 1, name = main} 2018-05-24 17:29:19.655 MultiThreadingDemo[4000:617158] BlockOperation2---{number = 3, name = (null)} 2018-05-24 17:29:19.655 MultiThreadingDemo[4000:617157] BlockOperation3---{number = 4, name = (null)}

结论:和NSInvocationOperation相比,NSBlockOperation对象不用添加到操作队列也能开启新线程,但是开启新线程是有条件的。前提是一个blockOperation中需要封装多个任务。如果只开启一个任务,默认会在当前线程执行。
NSOperation是基于GCD更高一层的封装,同样拥有任务的概念,那么我们可不可以简单的认为NSInvocationOperation类似于dispatch_sync,NSBlockOperation类似于dispatch_async呢?个人意见,欢迎指正。
1.3 自定义NSOperation子类 稍后更新...
2. NSOperationQueue
队列,回想GCD里面的dispatch_queue_t中...
切入正题:
NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。(copy自https://www.jianshu.com/p/4b1d77054b35)
2.1 NSOperationQueue创建 NSOperation 实现多线程的使用步骤分为三步:
  1. 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"Invocation1InQueue"}]; NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"Invocation2InQueue"}];

  1. 创建队列:创建 NSOperationQueue 对象。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  1. 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
[queue addOperation:operation1]; [queue addOperation:operation2];

2.2 NSOperationQueue中的依赖
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。
看下api中关于dependcy的几个方法
addDependency:添加依赖
removeDependency:移除依赖
NSArray *dependencies在当前操作开始执行之前完成执行的所有操作对象数组。比如A依赖B,B依赖C,那么A的dependencies就是BC
- (void)dependencyQueueTest { //依赖在开发中还是经常使用到的:有 A、B 两个操作,其中 A 执行完操作,B 才能执行操作 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //添加InvocationOperation NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"dependency1InQueue"}]; NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"dependency2InQueue"}]; //添加BlockOperation NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ [self taskTest:@{@"name":@"dependency3InQueue"}]; }]; [operation1 addDependency:operation3]; //operation1依赖operation3 [operation2 addDependency:operation1]; //operation2依赖operation1 [queue addOperation:operation1]; [queue addOperation:operation2]; [queue addOperation:operation3]; } //输出结果: 2018-05-24 18:11:48.881 MultiThreadingDemo[4000:656437] dependency3InQueue---{number = 5, name = (null)} 2018-05-24 18:11:50.945 MultiThreadingDemo[4000:656437] dependency3InQueue---{number = 5, name = (null)} 2018-05-24 18:11:53.012 MultiThreadingDemo[4000:656437] dependency1InQueue---{number = 5, name = (null)} 2018-05-24 18:11:55.085 MultiThreadingDemo[4000:656437] dependency1InQueue---{number = 5, name = (null)} 2018-05-24 18:11:57.114 MultiThreadingDemo[4000:680839] dependency2InQueue---{number = 15, name = (null)} 2018-05-24 18:11:59.147 MultiThreadingDemo[4000:680839] dependency2InQueue---{number = 15, name = (null)}

优先级NSOperationQueuePriority
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L,//最低 NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0,//默认 NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8//最高 };

2.3 优先级queuePriority queuePriority是NSOperation的属性,并不是Queue的,是operation在queue中的优先级
- (void)queuePriorityTest { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //添加operation1,设置operation1的优先级为VeryLow NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"PriorityVeryLow"}]; operation1.queuePriority = NSOperationQueuePriorityVeryLow; //添加operation2,默认优先级为normal NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskTest:) object:@{@"name":@"PriorityNormal"}]; //添加operation3,优先级为VeryHigh NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ [self taskTest:@{@"name":@"PriorityVeryHigh"}]; }]; operation3.queuePriority = NSOperationQueuePriorityVeryHigh; [queue addOperation:operation1]; [queue addOperation:operation2]; [queue addOperation:operation3]; } //输出结果 2018-05-24 18:17:55.987 MultiThreadingDemo[4000:656437] PriorityVeryLow---{number = 5, name = (null)} 2018-05-24 18:17:55.987 MultiThreadingDemo[4000:690639] PriorityNormal---{number = 16, name = (null)} 2018-05-24 18:17:55.987 MultiThreadingDemo[4000:690641] PriorityVeryHigh---{number = 17, name = (null)} 2018-05-24 18:17:57.996 MultiThreadingDemo[4000:656437] PriorityVeryLow---{number = 5, name = (null)} 2018-05-24 18:17:57.996 MultiThreadingDemo[4000:690639] PriorityNormal---{number = 16, name = (null)} 2018-05-24 18:17:57.996 MultiThreadingDemo[4000:690641] PriorityVeryHigh---{number = 17, name = (null)}

从结果看:queuePriority跟依赖是不一样的,并不是优先级高的先执行完再执行优先级低的。
2.4 最大并发操作数:maxConcurrentOperationCount
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。
这个比较简单,就先不上code了
可以对比一下GCD的信号量semaphore
2.5 线程之间通信 最常用的:异步操作完成之后回主线程更新UI
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:0.5]; // 模拟耗时操作 // 执行完当前任务刷新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"task1 have done"); }]; }];

2.6 线程锁 未完待续...
参考链接:(排名不分先后)
https://www.jianshu.com/p/4b1d77054b35
https://www.jianshu.com/p/e6cd25f6da91
https://www.jianshu.com/p/6861d36b1c8e

    推荐阅读