iOS - 多线程之 NSOperation
NSOperation
apple提供的多线程解决方案NSOperation是一个表示与单个任务关联的代码和数据的抽象类;因为是一个抽象类,所以不能直接使用,需要使用它的两个子类(NSInvocationOperation or NSBlockOperation) 去执行实际的操作任务;同样我们也可以通过自定义NSOperation。通常将操作添加到操作队列(NSOperationQueue类的实例)来执行操作。其实NSOperation就是对GCD的封装,相对于GCD来说可控性更强,并且可以加入操作依赖(addDependency: and removeDependency)。
NSInvocationOperation
start
- (void)demo1 {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(oprationTask:) object:@"InvocationOperation"];
[op start];
}
- (void)operationTask:(id)param {
NSLog(@"%@",[NSThread currentThread]);
}
输出结果:发现在主线程中输出的结果,但start方法是在当前线程中执行的
1
2018-05-19 14:45:07.956558+0800 NSOperation练习[1324:290009] <NSThread: 0x604000078000>{number = 1, name = main}
将操作添加到队列
- (void)demo2 {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(oprationTask:) object:@"InvocationOperation"];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];
}
输出结果:开启子线程异步执行
1
2018-05-19 14:50:53.013010+0800 NSOperation练习[1408:312296] <NSThread: 0x60400046b600>{number = 3, name = (null)}
NSBlockOperation (使用较多)
start
- (void)demo4 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
}
输出结果:start方法是在当前线程中执行
1
2018-05-19 15:06:11.837276+0800 NSOperation练习[1661:375754] <NSThread: 0x6000000745c0>{number = 1, name = main}
将操作添加到队列
- (void)demo5 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];
}
输出结果:开启子线程异步执行
1
2018-05-19 15:10:23.974301+0800 NSOperation练习[1720:389949] <NSThread: 0x60400027ea40>{number = 3, name = (null)}
执行块
- (void)demo6 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block 2 %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"block 1 %@", [NSThread currentThread]);
}];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:op];
NSLog(@"%@", op.executionBlocks);
}
输出结果:执行块和操作享有共同的属性设置,异步执行
1
2
3
4
5
6
2018-05-19 15:20:27.839119+0800 NSOperation练习[1882:431125] (
"<__NSGlobalBlock__: 0x1086f9080>",
"<__NSGlobalBlock__: 0x1086f90c0>"
)
2018-05-19 15:20:27.839131+0800 NSOperation练习[1882:431333] block 2 <NSThread: 0x60000027bbc0>{number = 3, name = (null)}
2018-05-19 15:20:27.839133+0800 NSOperation练习[1882:431330] block 1 <NSThread: 0x604000466fc0>{number = 4, name = (null)}
线程间通讯
- (void)demo7{
[[NSOperationQueue new] addOperationWithBlock:^{
NSLog(@"consuming time:%@",[NSThread currentThread]);
/// 回到主线程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"refresh ui: %@",[NSThread currentThread]);
}];
}];
}
监听block执行完成
- (void)demo13 {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i=0; i<5; ++i) {
NSLog(@"%zd %@",i,[NSThread currentThread]);
}
}];
//设置监听操作执行完成的block,必须要在把操作添加到队列之前设置
[op setCompletionBlock:^{
NSLog(@"setCompletionBlock %@",[NSThread currentThread]);
}];
[[NSOperationQueue new]addOperation:op];
}
输出结果:
1
2
3
4
5
6
2018-05-19 16:12:00.170053+0800 NSOperation练习[2607:585084] 0 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.170267+0800 NSOperation练习[2607:585084] 1 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.170410+0800 NSOperation练习[2607:585084] 2 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171319+0800 NSOperation练习[2607:585084] 3 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171538+0800 NSOperation练习[2607:585084] 4 <NSThread: 0x60000027bd80>{number = 3, name = (null)}
2018-05-19 16:12:00.171816+0800 NSOperation练习[2607:585083] setCompletionBlock <NSThread: 0x60000027bd40>{number = 4, name = (null)}
自定义NSOperation
自定义类继承NSOperation
@interface CustomOperation : NSOperation
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
DownloadOperation * op = [[DownloadOperation alloc]init];
[queue addOperation:op];
}
重写main方法
任何操作在执行时,首先会调用start方法,start方法会更新操作的状态(过滤操作);经start方法过滤之后,只有正常可执行的操作,就会调用这个main方法,重写操作的入口方法(main方法),就可以在这个方法里面指定操作执行的任务。main方法默认在子线程中异步执行
- (void)main
{
//在这个方法中做想要做的操作,即自定义 NSOperation的目的
NSLog(@"%@",self.URLString,[NSThread currentThread]);
}
NSOperationQueue
使用
NSOperationQueue只有一种类型,就是并发队列。在开发使用到NSOperationQueue时,建议将其定义为全局队列。
// 定义为属性
@property (nonatomic,strong) NSOperationQueue *queue;
- (NSOperationQueue * )queue
{
if (self.queue == nil) {
self.queue = [[NSOperationQueue alloc] init];
}
return self.queue;
}
最大并发数
maxConcurrentOperationCount是队列的一个属性,可以限制队列同时执行的任务数量,从而间接的控制了线程数量(线程可以复用),但队列最大并发数不是线程数。如果队列最大并发数设置为1,那么队列实际上就是一个串行队列了。
// 设置最大并发数 : 每次只能调度两个操作执行
queue.maxConcurrentOperationCount = 2;
验证队列的并发性
NSOperationQueue & NSInvocationOperation
- (void)demo8 {
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
for (NSInteger i=0; i<10; ++i) {
NSInvocationOperation * op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationTask:) object:@(i)];
[queue addOperation:op];
}
}
输出结果:会开启多条线程,不是顺序执行.与GCD中并发队列&异步执行效果一样,说明NSOperationQueue默认是并发执行
1
2
3
4
5
6
7
8
9
10
2018-05-19 15:01:47.478515+0800 NSOperation练习[1593:358939] <NSThread: 0x60400047a580>{number = 3, name = (null)}
2018-05-19 15:01:47.478518+0800 NSOperation练习[1593:358938] <NSThread: 0x600000263680>{number = 4, name = (null)}
2018-05-19 15:01:47.478519+0800 NSOperation练习[1593:358936] <NSThread: 0x60400047a500>{number = 6, name = (null)}
2018-05-19 15:01:47.478572+0800 NSOperation练习[1593:358935] <NSThread: 0x60400047a640>{number = 5, name = (null)}
2018-05-19 15:01:47.478740+0800 NSOperation练习[1593:358974] <NSThread: 0x600000263a40>{number = 7, name = (null)}
2018-05-19 15:01:47.478881+0800 NSOperation练习[1593:358938] <NSThread: 0x600000263680>{number = 4, name = (null)}
2018-05-19 15:01:47.478902+0800 NSOperation练习[1593:358939] <NSThread: 0x60400047a580>{number = 3, name = (null)}
2018-05-19 15:01:47.479084+0800 NSOperation练习[1593:358975] <NSThread: 0x6000002636c0>{number = 8, name = (null)}
2018-05-19 15:01:47.479096+0800 NSOperation练习[1593:358976] <NSThread: 0x600000263d00>{number = 10, name = (null)}
2018-05-19 15:01:47.479128+0800 NSOperation练习[1593:358977] <NSThread: 0x600000263dc0>{number = 9, name = (null)}
NSOperationQueue & NSBlockOperation
- (void)demo10 {
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
for (NSInteger i=0; i<10; ++i) {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:op];
}
}
输出结果:会开启多条线程,不是顺序执行.与GCD中并发队列&异步执行效果一样,说明NSOperationQueue默认是并发执行
1
2
3
4
5
6
7
8
9
10
2018-05-19 15:26:52.141566+0800 NSOperation练习[1984:456384] <NSThread: 0x60000027ef80>{number = 5, name = (null)}
2018-05-19 15:26:52.141566+0800 NSOperation练习[1984:456319] <NSThread: 0x604000468dc0>{number = 4, name = (null)}
2018-05-19 15:26:52.141569+0800 NSOperation练习[1984:456314] <NSThread: 0x60000027f040>{number = 3, name = (null)}
2018-05-19 15:26:52.141641+0800 NSOperation练习[1984:456313] <NSThread: 0x604000469d80>{number = 6, name = (null)}
2018-05-19 15:26:52.141661+0800 NSOperation练习[1984:456385] <NSThread: 0x60000027ee80>{number = 8, name = (null)}
2018-05-19 15:26:52.141676+0800 NSOperation练习[1984:456318] <NSThread: 0x604000469c40>{number = 7, name = (null)}
2018-05-19 15:26:52.141928+0800 NSOperation练习[1984:456384] <NSThread: 0x60000027ef80>{number = 5, name = (null)}
2018-05-19 15:26:52.141945+0800 NSOperation练习[1984:456319] <NSThread: 0x604000468dc0>{number = 4, name = (null)}
2018-05-19 15:26:52.141954+0800 NSOperation练习[1984:456314] <NSThread: 0x60000027f040>{number = 3, name = (null)}
2018-05-19 15:26:52.142048+0800 NSOperation练习[1984:456386] <NSThread: 0x60000027f380>{number = 9, name = (null)}
队列暂停继续和取消全部
isSuspended:
暂停和继续队列的属性;YES代表暂停队列,NO代表恢复队列。将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响 operationCount: 操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内
注意 : 如果先暂停队列,再添加操作到队列,队列不会调度操作执行.所以在暂停队列之前要判断队列中有没有任务.如果没有操作就不暂停队列.
#pragma mark - 演示队列的暂停
- (IBAction)pause:(id)sender
{
// 暂停队列之前判断队列中有无操作
if (self.queue.operationCount == 0) {
return;
}
// 暂停队列
self.queue.suspended = YES;
NSLog(@"pause %zd",self.queue.operationCount);
}
cancelAllOperations:
取消队列中的全部操作;旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外;正在执行的操作取消不了,如果要取消,需要自定义NSOperation;队列取消全部操作时,会有一定的时间延迟。
- (IBAction)cancelAll:(id)sender
{
[self.queue cancelAllOperations];
NSLog(@"cancelAll %zd",self.queue.operationCount);
}
qualityOfService
服务质量的枚举:
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
operation
让队列里面的操作有更多的机会被队列调度执行,类似于线程优先级
- (void)demo11 {
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 block %@",[NSThread currentThread]);
}];
for (NSInteger i=0; i<5; ++i) {
[op1 addExecutionBlock:^{
NSLog(@"opt1 %@",[NSThread currentThread]);
}];
}
op1.qualityOfService = NSQualityOfServiceBackground;
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 block %@",[NSThread currentThread]);
}];
for (NSInteger i=0; i<5; ++i) {
[op2 addExecutionBlock:^{
NSLog(@"opt2 %@",[NSThread currentThread]);
}];
}
op2.qualityOfService = NSQualityOfServiceUserInteractive;
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[op1,op2] waitUntilFinished:false];
}
输出结果:op2优先于op1执行
1
2
3
4
5
6
7
8
9
10
11
12
2018-05-19 16:05:46.292652+0800 NSOperation练习[2511:564443] op2 block <NSThread: 0x600000278580>{number = 4, name = (null)}
2018-05-19 16:05:46.292656+0800 NSOperation练习[2511:564453] opt2 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.292654+0800 NSOperation练习[2511:564445] opt2 <NSThread: 0x604000470ac0>{number = 6, name = (null)}
2018-05-19 16:05:46.292709+0800 NSOperation练习[2511:564454] opt2 <NSThread: 0x604000470c40>{number = 8, name = (null)}
2018-05-19 16:05:46.292933+0800 NSOperation练习[2511:564453] opt2 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.292937+0800 NSOperation练习[2511:564443] opt2 <NSThread: 0x600000278580>{number = 4, name = (null)}
2018-05-19 16:05:46.292944+0800 NSOperation练习[2511:564446] opt1 <NSThread: 0x604000470b00>{number = 5, name = (null)}
2018-05-19 16:05:46.292960+0800 NSOperation练习[2511:564444] op1 block <NSThread: 0x604000470b80>{number = 3, name = (null)}
2018-05-19 16:05:46.293083+0800 NSOperation练习[2511:564454] opt1 <NSThread: 0x604000470c40>{number = 8, name = (null)}
2018-05-19 16:05:46.293089+0800 NSOperation练习[2511:564453] opt1 <NSThread: 0x600000278680>{number = 7, name = (null)}
2018-05-19 16:05:46.294076+0800 NSOperation练习[2511:564446] opt1 <NSThread: 0x604000470b00>{number = 5, name = (null)}
2018-05-19 16:05:46.294207+0800 NSOperation练习[2511:564444] opt1 <NSThread: 0x604000470b80>{number = 3, name = (null)}
queue
- (void)demo12 {
NSOperationQueue * q1 = [[NSOperationQueue alloc]init];
NSOperationQueue * q2 = [[NSOperationQueue alloc]init];
q1.qualityOfService = NSQualityOfServiceBackground;
q2.qualityOfService = NSQualityOfServiceUserInteractive;
for (NSInteger i=0; i<5; ++i) {
[q1 addOperationWithBlock:^{
NSLog(@"q1");
}];
[q2 addOperationWithBlock:^{
NSLog(@"q2");
}];
}
}
输出结果:q2优先于q1执行
1
2
3
4
5
6
7
8
9
10
2018-05-19 16:05:11.965991+0800 NSOperation练习[2487:561497] q2 <NSThread: 0x604000274e00>{number = 4, name = (null)}
2018-05-19 16:05:11.965998+0800 NSOperation练习[2487:561496] q2 <NSThread: 0x600000275740>{number = 3, name = (null)}
2018-05-19 16:05:11.966106+0800 NSOperation练习[2487:561516] q2 <NSThread: 0x600000275940>{number = 5, name = (null)}
2018-05-19 16:05:11.966106+0800 NSOperation练习[2487:561517] q2 <NSThread: 0x604000274f80>{number = 6, name = (null)}
2018-05-19 16:05:11.966316+0800 NSOperation练习[2487:561516] q2 <NSThread: 0x600000275940>{number = 5, name = (null)}
2018-05-19 16:05:11.968284+0800 NSOperation练习[2487:561494] q1 <NSThread: 0x600000263840>{number = 7, name = (null)}
2018-05-19 16:05:11.968303+0800 NSOperation练习[2487:561495] q1 <NSThread: 0x604000275680>{number = 8, name = (null)}
2018-05-19 16:05:11.968376+0800 NSOperation练习[2487:561517] q1 <NSThread: 0x604000274f80>{number = 6, name = (null)}
2018-05-19 16:05:11.968409+0800 NSOperation练习[2487:561496] q1 <NSThread: 0x600000275740>{number = 3, name = (null)}
2018-05-19 16:05:11.969839+0800 NSOperation练习[2487:561494] q1 <NSThread: 0x600000263840>{number = 7, name = (null)}
支持KVO的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
isCancelled - read-only //是否取消
isAsynchronous - read-only //是否异步
isExecuting - read-only //是否正在执行
isFinished - read-only //是否结束
isReady - read-only //是否就绪
dependencies - read-only //依赖的其他的操作
queuePriority - readable and writable //队列优先级
completionBlock - readable and writable //结束回调
操作间依赖
需求实例
场景:用户需要先登录->付费->下载->通知用户
- (void)dependency
{
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"login %@",[NSThread currentThread]);
}];
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"pay %@",[NSThread currentThread]);
}];
NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download %@",[NSThread currentThread]);
}];
NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"notice user %@",[NSThread currentThread]);
}];
}
添加依赖
[op2 addDependency:op1]; // 操作2依赖于操作1
[op3 addDependency:op2]; // 操作3依赖于操作2
[op4 addDependency:op3]; // 操作4依赖于操作3
// waitUntilFinished : 是否等到指定的操作执行结束再执行后面的代码
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
// 通知用户的操作在主线程中执行
[[NSOperationQueue mainQueue] addOperation:op4];
// 验证 waitUntilFinished
NSLog(@"end");
小结
- 不能循环建立操作间依赖关系.否则,队列不调度操作执行
- 操作间可以跨队列建立依赖关系
- 要将操作间的依赖建立好了之后,再添加到队列中(先建立操作依赖关系,再把操作添加到队列)
NSOperation和GCD的区别
GCD
GCD iOS 4.0 推出,针对多核处理器的并发技术。GCD属于C语言的框架。将任务封装在block中,如果要停止已经加入 队列(queue) 的 任务(block) 需要写复杂的代码。只能设置队列的优先级不能设置任务的优先级。
高级功能
- barrier
- once
- after
- group
NSOperation
NSOperation iOS 2.0 推出,但在苹果推出 GCD 之后,对NSOperation的底层全部重写。NSOperation属于OC 框架,更加面向对象,底层是对 GCD 的封装。支持取消掉队列中的任务(正在执行的除外),还可以设置队列中每个操作的优先级
高级功能
- 最大操作并发数(GCD不好做)
- 继续/暂停/全部取消
- 跨队列设置操作的依赖关系

