套话
与GCD
一样,NSOperation
也是我们日常开发中经常用到的多线程技术。本文将会介绍NSOperation
的基本使用、添加依赖、
初次使用
NSOperation
是个抽象类,依赖于子类NSInvocationOperation
、NSBlockOperation
去实现
下面是开发者文档上对NSOperation
的一段描述
NSInvocationOperation
基本使用
- (void)test {// 处理事务NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:selfselector:@selector(handleInvocation:) object:@"Felix"];// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 操作加入队列[queue addOperation:op];
}- (void)handleInvocation:(id)operation {NSLog(@"%@ --- %@",operation, [NSThread currentThread]);
}
--------------------输出结果:-------------------
Felix --- <NSThread: 0x6000000422c0>{number = 3, name = (null)}
--------------------输出结果:-------------------
直接处理事务,不添加隐性队列
- (void)test {NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];[op start];
}
- (void)test {NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:op];[op start];
}
上述代码之所以会崩溃,是因为线程生命周期:
queue addOperation:op
已经将处理事务的操作任务加入到队列中,并让线程运行op start
将已经运行的线程再次运行会造成线程混乱
NSBlockOperation
NSInvocationOperation
和NSBlockOperation
两者的区别在于:
- 前者类似
target
形式 - 后者类似
block
形式——函数式编程,业务逻辑代码可读性更高
- (void)test {// 初始化添加事务NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"任务1————%@",[NSThread currentThread]);}];// 添加事务[bo addExecutionBlock:^{NSLog(@"任务2————%@",[NSThread currentThread]);}];// 回调监听bo.completionBlock = ^{NSLog(@"完成了!!!");};NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:bo];NSLog(@"事务添加进了NSOperationQueue");
}--------------------输出结果:-------------------
事务添加进了NSOperationQueue
任务1————<NSThread: 0x6000032dc1c0>{number = 5, name = (null)}
任务2————<NSThread: 0x6000032a1880>{number = 4, name = (null)}
完成了!!!
--------------------输出结果:-------------------
NSOperationQueue是异步执行的,所以任务一
、任务二
的完成顺序不确定
执行顺序
下列代码可以证明操作与队列的执行效果是异步并发
的
- (void)test {NSOperationQueue *queue = [[NSOperationQueue alloc] init];for (int i = 0; i < 5; i++) {[queue addOperationWithBlock:^{NSLog(@"%@---%d", [NSThread currentThread], i);}];}
}
--------------------输出结果:-------------------
<NSThread: 0x600002771600>{number = 3, name = (null)}---0
<NSThread: 0x60000277ac80>{number = 7, name = (null)}---3
<NSThread: 0x600002774840>{number = 6, name = (null)}---2
<NSThread: 0x600002776a80>{number = 8, name = (null)}---4
<NSThread: 0x60000270c540>{number = 5, name = (null)}---1
--------------------输出结果:-------------------
设置优先级
NSOperation
设置优先级只会让CPU
有更高的几率调用,不是说设置高就一定全部先完成
- 不使用
sleep
——高优先级的任务一
先于低优先级的任务二
- (void)test {NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 5; i++) {//sleep(1);NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);}}];// 设置最高优先级bo1.qualityOfService = NSQualityOfServiceUserInteractive;NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 5; i++) {NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);}}];// 设置最低优先级bo2.qualityOfService = NSQualityOfServiceBackground;NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:bo1];[queue addOperation:bo2];
}--------------------输出结果:-------------------
第一个操作 0 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 1 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 2 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 3 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 4 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第二个操作 0 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操作 1 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操作 2 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操作 3 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操作 4 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
--------------------输出结果:-------------------
- 使用
sleep
进行延时——高优先级的任务一
慢于低优先级的任务二
第二个操作 0 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操作 1 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操作 2 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操作 3 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操作 4 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第一个操作 0 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 1 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 2 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 3 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 4 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
线程间通讯
- 在
GCD
中使用异步进行网络请求,然后回到主线程刷新UI NSOperation
中也有类似在线程间通讯的操作
- (void)test {NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.name = @"Felix";[queue addOperationWithBlock:^{NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);[[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);}];}];
}
--------------------输出结果:-------------------
请求网络<NSOperationQueue: 0x7ff4a240bae0>{name = 'Felix'}--<NSThread: 0x6000007dcf00>{number = 5, name = (null)}
刷新UI<NSOperationQueue: 0x7ff4a24087d0>{name = 'NSOperationQueue Main Queue'}--<NSThread: 0x60000078c8c0>{number = 1, name = main}
--------------------输出结果:-------------------
设置并发数
- 在
GCD
中只能使用信号量来设置并发数 - 而
NSOperation
轻易就能设置并发数 -
- 通过
设置maxConcurrentOperationCount
来控制单次出队列去执行的任务数
- 通过
- (void)test {NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.name = @"Felix";queue.maxConcurrentOperationCount = 2;for (int i = 0; i < 5; i++) {[queue addOperationWithBlock:^{ // 一个任务[NSThread sleepForTimeInterval:2];NSLog(@"%d-%@",i,[NSThread currentThread]);}];}
}
--------------------输出结果:-------------------
1-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
0-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
3-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
2-<NSThread: 0x60000094b0c0>{number = 7, name = (null)}
4-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
--------------------输出结果:-------------------
添加依赖
在NSOperation
中添加依赖能很好的控制任务执行的先后顺序
- (void)test {NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"请求token");}];NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"拿着token,请求数据1");}];NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"拿着数据1,请求数据2");}];//添加依赖[bo2 addDependency:bo1];[bo3 addDependency:bo2];[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];NSLog(@"执行完了?我要干其他事");
}--------------------输出结果:-------------------
请求token
拿着token,请求数据1
拿着数据1,请求数据2
执行完了?我要干其他事
--------------------输出结果:-------------------
注意不要添加依赖导致循环运用,会导致依赖无效并会在控制台打印出"XPC connection interrupted"
任务的挂起、继续、取消
// 挂起
queue.suspended = YES;
// 继续
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
这幅图是并发量为2的情况:
- 挂起前:任务3、任务4等待被调度
- 挂起瞬间:任务3、任务4已经被调度出队列,准备执行,此时它们是无法挂起的
- 挂起后:任务3、任务4被线程执行,而原来的队列被挂起不能被调度
自定义NSOperation缓存机制
NSOperation异步处理
NSOperationQueue *queue = [[NSOperationQueue alloc] init];UIImageView *imageView = [[UIImageView alloc] init];imageView.frame = CGRectMake(100, 100, 100, 100);[self.view addSubview:imageView];NSString *urlStr = [NSString stringWithFormat:@"https://profile-avatar.csdnimg.cn/910a183044d54231b5e418c23a558516_weixin_61196797.jpg!1"];NSURL *imageURL = [NSURL URLWithString:urlStr];NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"去下载图片" );// 延迟NSData *data = [NSData dataWithContentsOfURL:imageURL];UIImage *image = [UIImage imageWithData:data];// 更新UI[[NSOperationQueue mainQueue] addOperationWithBlock:^{imageView.image = image;NSLog(@"加载完成");imageView.backgroundColor = [UIColor redColor];}];}];[queue addOperation:bo];