NSOperation简介
NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。
1. 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 实现多线程的使用步骤分为三步:
- 创建操作:先将需要执行的操作封装到一个 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"}];
- 创建队列:创建 NSOperationQueue 对象。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
[queue addOperation:operation1];
[queue addOperation:operation2];
2.2 NSOperationQueue中的依赖
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。
看下api中关于dependcy的几个方法
addDependency:
添加依赖
removeDependency:
移除依赖
NSArray
在当前操作开始执行之前完成执行的所有操作对象数组。比如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