NSOperation简介
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高
为什么要使用 NSOperation、NSOperationQueue?
- 可添加完成的代码块,在操作完成后执行。
- 添加操作之间的依赖关系,方便的控制执行顺序。
- 设定操作执行的优先级。
- 可以很方便的取消一个操作的执行。
- 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
NSOperation、NSOperationQueue 操作和操作队列
既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。
- 操作(Operation)
- 执行操作的意思,换句话说就是你在线程中执行的那段代码。
- 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
- 操作队列(Operation Queues)
- 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
- 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
- NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
- NSOperation、NSOperationQueue 使用步骤
- NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。
- NSOperation 实现多线程的使用步骤分为三步
- 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
- 创建队列:创建 NSOperationQueue 对象。
- 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
- 之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。
NSOperation 和 NSOperationQueue 基本使用
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。
- 使用子类 NSInvocationOperation
- 使用子类 NSBlockOperation
- 自定义继承 NSOperation 的子类,通过实现内部相应的方法来封装操作。
使用子类 NSInvocationOperation
/**
NSInvocationOperation : 创建操作 ---> 创建队列 ---> 操作加入队列
*/
- (void)demo{
//1:创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
//2:创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//3:操作加入队列 --- 操作会在新的线程中
[queue addOperation:op];
}
- (void)handleInvocation:(id)op{
NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
复制代码
打印:
2020-01-29 15:43:11.012492+0800 001---NSOperation初体验[1628:78932] 123 --- <NSThread: 0x600001ea9080>{number = 3, name = (null)}
复制代码
NSInvocationOperation也可以手动调起,这样不会开启线程,会在当前队列执行任务
/**
手动调起操作
*/
- (void)demo1{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
// 注意 : 如果该任务已经添加到队列,你再手动调回出错
// NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// [queue addOperation:op];
// 手动调起操作,默认在当前线程
[op start];
}
- (void)handleInvocation:(id)op{
NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}
复制代码
打印:
2020-01-29 15:52:55.404726+0800 001---NSOperation初体验[1740:83188] 123 --- <NSThread: 0x60000024e940>{number = 1, name = main}
复制代码
注意:我们尽量不要用这种方式去执行任务,如果该任务已经添加到队列,再手动调起任务程序会崩溃
使用子类 NSBlockOperation
/**
blockOperation 初体验
*/
- (void)demo2{
//1:创建blockOperation
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@ -- %@",[NSThread mainThread],[NSThread currentThread]);
}];
//1.1 设置监听
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};
//2:创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//3:添加到队列
[queue addOperation:bo];
}
复制代码
打印:
2020-01-29 15:57:30.760964+0800 001---NSOperation初体验[1812:85696] <NSThread: 0x600001a66940>{number = 1, name = (null)} -- <NSThread: 0x600001a2dec0>{number = 3, name = (null)}
2020-01-29 15:57:30.761098+0800 001---NSOperation初体验[1812:85697] 完成了!!!
复制代码
NSBlockOperation也可以使用start方法手动执行,这样不会开启线程,会在当前队列执行任务。如果该任务已经添加到队列中,手动调起任务程序会崩溃
自定义继承 NSOperation 的子类,通过实现内部相应的方法来封装操作。
如果使用子类 NSInvocationOperation、NSBlockOperation 不能满足日常需求,我们可以使用自定义继承自 NSOperation 的子类。
先定义一个继承 NSOperation 的子类,重写main方法。
// NHOperation.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NHOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
// NHOperation.m
#import "NHOperation.h"
@implementation NHOperation
- (void)main{
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}
}
@end
复制代码
导入头文件NHOperation.h
- (void)demo3{
NHOperation *nhQueue = [[NHOperation alloc]init];
[nhQueue start];
}
复制代码
打印:
2020-01-29 16:20:57.036713+0800 001---NSOperation初体验[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
2020-01-29 16:20:59.037879+0800 001---NSOperation初体验[2060:94891] 1---<NSThread: 0x600001c16940>{number = 1, name = main}
复制代码
这种自定义继承NSOperation的写法可以自己掌握线程的生命周期,SDWebImage框架重写了start,main等一系列方法,去实现下载图片模块
可执行代码块
/**
可执行代码块
*/
- (void)demo5{
//创建操作
NSBlockOperation *ob = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//添加执行代码块
[ob addExecutionBlock:^{
NSLog(@"这是一个执行代码块 - %@",[NSThread currentThread]);
}];
// 执行代码块 在新的线程 创建的操作在当前线程
//[ob start];
//利用队列,两个都在新的线程中
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:ob];
}
复制代码
打印:
2020-01-29 16:37:49.565971+0800 001---NSOperation初体验[2207:101558] <NSThread: 0x600002457d40>{number = 3, name = (null)}
2020-01-29 16:37:49.565991+0800 001---NSOperation初体验[2207:101555] 这是一个执行代码块 - <NSThread: 0x600002458480>{number = 4, name = (null)}
复制代码
线程间通讯
/**
线程通讯
*/
- (void)demo6{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Noah";
[queue addOperationWithBlock:^{
NSLog(@"%@ = %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%@ --%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}
复制代码
打印:
2020-01-29 16:38:41.372075+0800 001---NSOperation初体验[2239:102618] <NSOperationQueue: 0x6000015ee560>{name = 'Noah'} = <NSThread: 0x6000000f9880>{number = 3, name = (null)}
2020-01-29 16:38:41.378217+0800 001---NSOperation初体验[2239:102581] <NSOperationQueue: 0x6000015ea180>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600000086940>{number = 1, name = main}
复制代码
优先级设定
/**
优先级,只会让CPU有更高的几率调用,不是说设置高就一定全部先完成
*/
- (void)demo4{
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"第1个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置优先级 - 最高
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
//创建第二个操作
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置优先级 - 最低
bo2.qualityOfService = NSQualityOfServiceBackground;
//2:创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//3:添加到队列
[queue addOperation:bo1];
[queue addOperation:bo2];
}
复制代码
打印:
2020-01-29 16:42:21.830886+0800 001---NSOperation初体验[2296:104794] 第1个操作 0 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.830901+0800 001---NSOperation初体验[2296:104795] 第2个操作 0 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831015+0800 001---NSOperation初体验[2296:104794] 第1个操作 1 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831076+0800 001---NSOperation初体验[2296:104794] 第1个操作 2 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831107+0800 001---NSOperation初体验[2296:104795] 第2个操作 1 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831135+0800 001---NSOperation初体验[2296:104794] 第1个操作 3 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831217+0800 001---NSOperation初体验[2296:104794] 第1个操作 4 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831241+0800 001---NSOperation初体验[2296:104795] 第2个操作 2 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831306+0800 001---NSOperation初体验[2296:104794] 第1个操作 5 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831351+0800 001---NSOperation初体验[2296:104795] 第2个操作 3 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831387+0800 001---NSOperation初体验[2296:104794] 第1个操作 6 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831438+0800 001---NSOperation初体验[2296:104795] 第2个操作 4 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.831595+0800 001---NSOperation初体验[2296:104794] 第1个操作 7 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831771+0800 001---NSOperation初体验[2296:104794] 第1个操作 8 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.831957+0800 001---NSOperation初体验[2296:104795] 第2个操作 5 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834469+0800 001---NSOperation初体验[2296:104794] 第1个操作 9 --- <NSThread: 0x6000003f7200>{number = 3, name = (null)}
2020-01-29 16:42:21.834487+0800 001---NSOperation初体验[2296:104795] 第2个操作 6 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834579+0800 001---NSOperation初体验[2296:104795] 第2个操作 7 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834659+0800 001---NSOperation初体验[2296:104795] 第2个操作 8 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
2020-01-29 16:42:21.834761+0800 001---NSOperation初体验[2296:104795] 第2个操作 9 --- <NSThread: 0x600000384a80>{number = 4, name = (null)}
复制代码
设置队列最大并发数
- (void)demo7{
NSOperationQueue *queue = [NSOperationQueue new];
// 设置队列最大并发数为2
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i<10; i++) {
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
复制代码
打印:
2020-01-29 16:45:11.512510+0800 001---NSOperation初体验[2360:107175] 0-<NSThread: 0x6000023c2140>{number = 3, name = (null)}
2020-01-29 16:45:11.512505+0800 001---NSOperation初体验[2360:107173] 1-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:13.514845+0800 001---NSOperation初体验[2360:107172] 2-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:13.514883+0800 001---NSOperation初体验[2360:107174] 3-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初体验[2360:107173] 4-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:15.516537+0800 001---NSOperation初体验[2360:107172] 5-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初体验[2360:107173] 7-<NSThread: 0x6000023d1280>{number = 4, name = (null)}
2020-01-29 16:45:17.518188+0800 001---NSOperation初体验[2360:107174] 6-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初体验[2360:107172] 8-<NSThread: 0x6000023c4980>{number = 5, name = (null)}
2020-01-29 16:45:19.521176+0800 001---NSOperation初体验[2360:107174] 9-<NSThread: 0x6000023fc380>{number = 6, name = (null)}
复制代码
任务相互之间的依赖关系
- (void)demo{
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];
//注意这里一定不要构成循环依赖 : 不会报错,但是所有操作不会执行
//[bo1 addDependency:bo3];
//waitUntilFinished 堵塞线程
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:NO];
NSLog(@"执行完了?我要干其他事");
}
复制代码
打印:
2020-01-29 16:47:41.371621+0800 002---NSOperation深入浅出[2422:109106] 执行完了?我要干其他事
2020-01-29 16:47:41.872960+0800 002---NSOperation深入浅出[2422:109157] 请求token
2020-01-29 16:47:42.374790+0800 002---NSOperation深入浅出[2422:109156] 拿着token,请求数据1
2020-01-29 16:47:42.877750+0800 002---NSOperation深入浅出[2422:109156] 拿着数据1,请求数据2
复制代码
NSOperationQueue的挂起,继续,取消
/**
关于operationQueue的挂起,继续,取消
*/
- (void)demo1{
self.queue.name = @"Cooci";
self.queue.maxConcurrentOperationCount = 2;
for (int i=0; i< 20; i++) {
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@-----%d",[NSThread currentThread],i);
}];
}
}
- (IBAction)pauseOrContinue:(id)sender {
if (self.queue.operationCount == 0) {
NSLog(@"当前没有操作,没有必要挂起和继续");
return;
}
// 一个细节 正在执行的操作无法挂起
if (self.queue.suspended) {
NSLog(@"当前是挂起状态,准备继续");
}else{
NSLog(@"当前为执行状态,准备挂起");
}
self.queue.suspended = !self.queue.isSuspended;
}
- (IBAction)cancel:(id)sender {
[self.queue cancelAllOperations];
}
复制代码
打印:
2020-01-29 16:52:44.854415+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----1
2020-01-29 16:52:44.854415+0800 002---NSOperation深入浅出[2488:111998] <NSThread: 0x600001d1da40>{number = 4, name = (null)}-----0
2020-01-29 16:52:45.856950+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----2
2020-01-29 16:52:45.856988+0800 002---NSOperation深入浅出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----3
2020-01-29 16:52:46.277210+0800 002---NSOperation深入浅出[2488:111955] 当前为执行状态,准备挂起
2020-01-29 16:52:46.857767+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----4
2020-01-29 16:52:46.857782+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----5
2020-01-29 16:52:48.967113+0800 002---NSOperation深入浅出[2488:111955] 当前是挂起状态,准备继续
2020-01-29 16:52:49.968072+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----6
2020-01-29 16:52:49.968087+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----7
2020-01-29 16:52:50.969946+0800 002---NSOperation深入浅出[2488:112000] <NSThread: 0x600001d04280>{number = 6, name = (null)}-----8
2020-01-29 16:52:50.969946+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----9
2020-01-29 16:52:51.465420+0800 002---NSOperation深入浅出[2488:111955] 全部取消
2020-01-29 16:52:51.973286+0800 002---NSOperation深入浅出[2488:111999] <NSThread: 0x600001d1f080>{number = 5, name = (null)}-----10
2020-01-29 16:52:51.973297+0800 002---NSOperation深入浅出[2488:112001] <NSThread: 0x600001d57180>{number = 3, name = (null)}-----11
复制代码