深入浅出iOS多线程(一)——线程的概念
深入浅出iOS多线程(二)——pthraed和NSThread的使用
深入浅出iOS多线程(三)——GCD多线程
深入浅出iOS多线程(四)——NSOperation多线程
深入浅出iOS多线程(五)——多线程锁
NSOperation的作用
简介
和GCD一样,NSOperation也是并发编程技术,NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,比GCD更加简单,更加方便
NSoperation需要配合NSOperationQueue来实现多线程,NSOperation单独使用时系统同步执行操作,并没有开辟新的线程的能力,只有配合NSoperationQueue才能实现异步执行。
NSOperation是苹果大力推荐的"并发"技术
NSOperation的核心概念是,将"操作"添加进"队列"
GCD将"任务"添加到"队列"
- NSOperation是一个抽象类
- 特点:不能直接使用
- 目的:定义子类共有的属性和方法
- 子类:NSInvocationOperation、NSBlockOperation
由于NSOperation是基于GCD的,使用步骤也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列,NSOperation实现多线程的使用步骤分3步:
- 创建操作:创建
NSOperation
子类对象 - 创建队列:创建
NSOperationQueue
队列 - 将操作添加到队列中去:
NSOperation
添加到NSOperationQueue
中去
系统会自动取出"队列"中的"操作"执行。
NSOperation多线程的基本使用
NSInvocationOperation的基本使用
-
将NSOperation添加到队列,会自动异步执行调度方法
NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"]; NSOperationQueue * q = [[NSOperationQueue alloc]init]; [q addOperation:op];
-
打印结果
<NSThread: 0x600001f536c0>{number = 3, name = (null)} invocation
将NSOperation添加到队列,会自动异步执行调度方法
根据上述代码我们可以确信NSOperationQueue
是一个队列,而NSInvocationOperation
是一个操作(任务),那么这个队列到底是什么样的队列,而这个操作是什么样的操作,在上述代码中队列添加更多的"操作",代码如下:
//队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
for (int i = 0; i<10; i++) {
NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
//将操作添加到队列 - 会自动异步执行调度方法
[q addOperation:op];
}
打印结果:
<NSThread: 0x6000016c8cc0>{number = 4, name = (null)} 1
<NSThread: 0x6000016f4140>{number = 3, name = (null)} 0
<NSThread: 0x6000016c0a00>{number = 6, name = (null)} 3
<NSThread: 0x6000016cc380>{number = 5, name = (null)} 2
<NSThread: 0x6000016f4280>{number = 7, name = (null)} 4
<NSThread: 0x6000016f4140>{number = 3, name = (null)} 8
<NSThread: 0x6000016cc380>{number = 5, name = (null)} 6
<NSThread: 0x6000016c0a00>{number = 6, name = (null)} 7
<NSThread: 0x6000016c8cc0>{number = 4, name = (null)} 5
<NSThread: 0x6000016f4280>{number = 7, name = (null)} 9
从打印结果上面看,线程不一样,执行顺序也不一样,说明这个队列是并发队列,队列中的操作是异步操作。
NSBlockOperation的基本使用
NSBlockOperation
跟NSInvocationOperation
使用步骤几乎一摸一样,不一样的地方在于,NSBlockOperation
比NSInvocationOperation
省略了一个@selector,代码更加简洁。
/**
第一种block形式
**/
//1.队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
//2.操作
for (int i = 0; i<10; i++) {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread currentThread].name = [@(i) stringValue];
NSLog(@"%@",[NSThread currentThread]);
}];
//将操作添加到队列 - 会自动异步执行调度方法
[q addOperation:op];
}
/**
第一种block形式
**/
//1.队列
NSOperationQueue * q = [[NSOperationQueue alloc]init];
//2.操作
for (int i = 0; i<10; i++) {
//将操作添加到队列 - 会自动异步执行调度方法
[q addOperationWithBlock:^{
[NSThread currentThread].name = [@(i) stringValue];
NSLog(@"%@",[NSThread currentThread]);
}];
}
打印结果:
<NSThread: 0x6000001e1900>{number = 5, name = 1}
<NSThread: 0x6000001e1940>{number = 6, name = 3}
<NSThread: 0x6000001ea900>{number = 3, name = 0}
<NSThread: 0x6000001e16c0>{number = 4, name = 2}
<NSThread: 0x6000001ea900>{number = 3, name = 5}
<NSThread: 0x6000001e1940>{number = 6, name = 4}
<NSThread: 0x6000001e1900>{number = 5, name = 6}
<NSThread: 0x6000001e16c0>{number = 4, name = 7}
<NSThread: 0x6000001eaf40>{number = 7, name = 8}
<NSThread: 0x6000001ea900>{number = 3, name = 9}
上述代码运行结果,同样可以知道这个队列是并发队列,队列中的操作是异步操作。
上述代码,所有的代码都要添加一个队列怎么办?
- 在
self
中定义一个类属性,在配合懒加载的形式,这样的话不管这个类中哪个方法有添加队列的操作,那么都可以使用这一个"队列",这样会让代码更加简洁。
@interface ViewController ()
@property (nonatomic,strong)NSOperationQueue * MyQueue;
@end
- (NSOperationQueue *)MyQueue{
if(!_MyQueue){
_MyQueue = [[NSOperationQueue alloc]init];
}
return _MyQueue;
}
for (int i = 0; i<10; i++) {
//将操作添加到队列 - 会自动异步执行调度方法
[self.MyQueue addOperationWithBlock:^{
[NSThread currentThread].name = [@(i) stringValue];
NSLog(@"%@",[NSThread currentThread]);
}];
}
很简洁很爽
NSOperation线程间的通信
//小清新
[self.MyQueue addOperationWithBlock:^{
NSLog(@"耗时操作");
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSLog(@"更新UI");
}];
}];
NSOperation最大并发数
- 从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
- 在 iOS 7.0 以前,GCD 通常只会开启 5 6条线程!
- 目前线程多了说明:
- 底层的现场池更大了,能够拿到的线程资源多了!
- 多控制同时并发的现场数,要求就更高了!
//添加操作进队列
/*
从 iOS 8.0 开始,无论使用 GCD还是 NSOperation ,都会开启很多线程
在 iOS 7.0 以前,GCD 通常只会开启 5 6条线程!
目前线程多了说明:
1.底层的现场池更大了,能够拿到的线程资源多了!
2.多控制同时并发的现场数,要求就更高了!
*/
for (int i = 0;i < 20; i++) {
[self.MyQueue addOperationWithBlock:^{
NSLog(@"%@---%d",[NSThread currentThread],i);
}];
}
打印结果:
<NSThread: 0x600003e984c0>{number = 4, name = (null)}---1
<NSThread: 0x600003e98540>{number = 5, name = (null)}---2
<NSThread: 0x600003e96980>{number = 6, name = (null)}---3
<NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---0
<NSThread: 0x600003e984c0>{number = 4, name = (null)}---4
<NSThread: 0x600003e96980>{number = 6, name = (null)}---5
<NSThread: 0x600003e98540>{number = 5, name = (null)}---6
<NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---7
<NSThread: 0x600003e984c0>{number = 4, name = (null)}---8
<NSThread: 0x600003e96980>{number = 6, name = (null)}---9
<NSThread: 0x600003e98540>{number = 5, name = (null)}---10
<NSThread: 0x600003e9e280>{number = 7, name = (null)}---11
<NSThread: 0x600003e8f080>{number = 8, name = (null)}---12
<NSThread: 0x600003e8f280>{number = 9, name = (null)}---13
<NSThread: 0x600003e9c6c0>{number = 3, name = (null)}---14
<NSThread: 0x600003e984c0>{number = 4, name = (null)}---15
<NSThread: 0x600003e98540>{number = 5, name = (null)}---16
<NSThread: 0x600003e9e700>{number = 10, name = (null)}---17
<NSThread: 0x600003e9e740>{number = 11, name = (null)}---18
<NSThread: 0x600003e96980>{number = 6, name = (null)}---19
-
通常设置同时最大的并发操作数量
- WIFI: 5 至 6
- 流量 : 2 到 3
self.MyQueue.maxConcurrentOperationCount = 2; for (int i = 0;i < 20; i++) { [self.MyQueue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"%@---%d",[NSThread currentThread],i); }]; }
打印结果:
<NSThread: 0x6000002d4f40>{number = 4, name = (null)}---0
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---1
<NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---2
<NSThread: 0x6000002d5000>{number = 5, name = (null)}---3
<NSThread: 0x6000002d5000>{number = 5, name = (null)}---5
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---4
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---7
<NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---6
<NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---9
<NSThread: 0x6000002d4f40>{number = 4, name = (null)}---8
<NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---10
<NSThread: 0x6000002d4f40>{number = 4, name = (null)}---11
<NSThread: 0x6000002d9ec0>{number = 6, name = (null)}---13
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---12
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---15
<NSThread: 0x6000002d4f40>{number = 4, name = (null)}---14
<NSThread: 0x6000002d5000>{number = 5, name = (null)}---16
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---17
<NSThread: 0x6000002ad700>{number = 3, name = (null)}---19
<NSThread: 0x6000002d4f40>{number = 4, name = (null)}---18
为什么实际当中打印会多出几个线程?
- 线程中的任务完成以后会回收线程
- 当一个任务完成需要从队列中取新的任务,取一个空间的线程,或者是开辟新线程的时候,而1中的线程正在回收,所以这个任务开辟的线程会比原先的线程number+1
NSOperationQueue
的属性和方法
suspended
-
NSOperationQueue
的一个属性,可以控制NSOperationQueue
队列的挂起还是继续- isSuspended 判断是否是挂起
- suspended 修改
NSOperationQueue
挂起或者继续
//队列是否挂起 if(self.MyQueue.isSuspended){ NSLog(@"继续"); self.MyQueue.suspended = NO; }else{ NSLog(@"暂停"); self.MyQueue.suspended = YES; } -(void)demo1{ self.MyQueue.maxConcurrentOperationCount = 2; for (int i = 0;i < 20; i++) { [self.MyQueue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"%@---%d",[NSThread currentThread],i); }]; } }
operationCount
可以拿到队列中的操作数
NSLog(@"%tu",self.MyQueue.operationCount);
取消NSOperationQueue
中的所有操作
注意:
- 队列挂起的时候,取消队列所有操作,不会清空队列的
operationCoount
只有在队列继续的时候才能清空 - 正在执行的操作也不会被清空,也不会被取消
[self.MyQueue cancelAllOperations];
NSOperation
依赖关系(Dependency)
waitUntilFinished中的YES会卡住当前线程
[self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
NSOperation依赖关系:
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
例子:
/*
* 例子:下载/解压/通知用户
**/
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"解压----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户----%@",[NSThread currentThread]);
}];
//NSOperation 提供了依赖关系
//注意:不要指定循环依赖,队列就不工作了,不会造成死锁。
[op2 addDependency:op1];
[op3 addDependency:op2];
//YES 会卡住当前线程
[self.MyQueue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
NSLog(@"come here %@",[NSThread currentThread]);
//主线程通知用户
[[NSOperationQueue mainQueue]addOperation:op3];
打印结果:
下载----<NSThread: 0x600002dc3240>{number = 3, name = (null)}
解压----<NSThread: 0x600002dc5440>{number = 4, name = (null)}
通知用户----<NSThread: 0x600002dc3240>{number = 3, name = (null)}
come here <NSThread: 0x600002d9cb00>{number = 1, name = main}
总结
- 只要是
NSOperation
的子类,就可以添加到NSOperationQueue
。 - 如果使用
NSOperation
可以使代码更加简洁,代码会更加工整,NSOperation
能办到的事情,GCD
也都可以办到。 -
NSOperation
相比GCD
节约来代码行数,并且是面向对象的。 -
NSOperation
是苹果大力推荐的并发
技术。 - NSOperation是一个抽象类
- 特点:不能直接使用
- 目的:定义子类共有的属性和方法
- 子类:NSInvocationOperation、NSBlockOperation
GCD 和 NSOperation 对比
GCD 在 iOS 4.0 推出,主要针对多核处理器做了优化的并发技术,是C语言的
- 将"任务"[block]添加到 队列[串行/并发/主队列/全局队列] ,并且指定执行任务的函数[同步/异步]
- 线程间的通讯 dispatch_get_main_queue()
- 提供了一些 NSOperation 不具备的功能
- 一次执行
- 延迟执行
- 调度组(在op中也可以做到,有点麻烦)
- 信号量
- apply(重复)
NSOperation 在 iOS 2.0 推出的,苹果推出 GCD以后,对NSOperation 底层做了重写!
- 将操作[异步执行的任务] 添加到队列[并发队列],就会立刻异步执行
- mainQueue
- 提供了一些GCD 实现起来比较困难的功能
- 最大并发线程
- 队列的暂停/继续
- 取消所有操作
- 指定操作之间的依赖关系(GCD 用同步来实现)