前言
本文主要介绍iOS多线程开发中使用的主要技术:NSOperation, GCD。 NSThread, pthread。 内容依照开发中的优先推荐使用的顺序进行介绍,涉及多线程底层知识比較多的NSThread, pthread 放到了后面。建议小伙伴们先看文件夹。依据自己的需求来阅读。
NSOperation
简单介绍
使用NSOperation和NSOperationQueue能简单高效的实现多线程编程
NSOperation和NSOperationQueue实现多线程的详细步骤:
(1)先将须要运行的操作封装到一个NSOperation对象中
(2)然后将NSOperation对象加入到NSOperationQueue中 (3)系统会⾃动将NSOperationQueue中的NSOperation取出来 (4)将取出的NSOperation封装的操作放到⼀条新线程中执⾏
NSOperation的子类
NSOperation是个抽象类,并不具备封装操作的能力,必须使⽤它的子类
(1)不能直接使用(方法没有实现)
(2)约束子类都具有共同的属性和方法
使用NSOperation⼦类的方式有3种:
(1)NSInvocationOperation
(2)NSBlockOperation (3)自己定义子类继承NSOperation,实现内部对应的⽅法
NSInvocationOperation
创建NSInvocationOperation 对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
调用start 方法開始运行操作
- (void)start;
一旦运行操作,就会调用target 的 selector 方法
注意:
默认情况下,调用start方法后并不会开一条新线程去运行操作,而是当前线程同步运行操作;仅仅有将NSOperation 加入到NSOperationQuene中。才会异步运行操作;
1. 运行操作
//创建操作NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"]; //在当前线//程运行方法(開始运行操作)[op start];
2. 把操作加入到队列(并開始异步运行)
//创建操作 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"]; //将操作加入到队列,会自己主动异步调用方法 NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:op]; - (void)downloadFile:(id)object{ NSLog(@"下载:%@----线程:%@",object,[NSThread currentThread]); }
3. 开启多个线程,不会顺序运行
我们要记住:
NSOperation是对GCD的封装。而GCD并发队列,异步运行。
//队列NSOperationQueue *queue = [[NSOperationQueue alloc] init]; for (int i = 0; i < 10; i++) { //创建操作 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@(i)]; //将操作加入到队列,会自己主动异步调用方法 [queue addOperation:op]; } - (void)downloadFile:(id)object{ NSLog(@"下载:%@----线程:%@",object,[NSThread currentThread]); }
注意:
默认情况下。假设操作没有放到队列中queue中,都是同步运行。仅仅有将NSOperation放到一个NSOperationQueue中,才会异步运行操作
加入操作到NSOperationQueue中
- (void)addNOperation:(NSOperation *)op;- (void)addOperationWithBlock:(void(^)(void))block
NSBlockOperation
创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void)^(void)
通过addExecutionBlock 方法加入很多其它的操作
- (id)addExecutionBlock:(void)(^)(void)block
注意:仅仅要在NSBlockOperation封装的操作数>1, 就会一步运行操作;
1. NSBlockOperation
NSOperationQueue *queue = [[NSOperationQueue alloc] init];for (int i = 0; i < 10; i++){ //创建操作 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"down %d %@",i,[NSThread currentThread]); }]; //把操作加入到队列中 [queue addOperation:op]; }
2. NSOperationQueue加入block的operation
以下我们直接把操作的block代码块加到队列中,是不是代码更简洁啦,block是不是用起来非常爽-_-
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; for (int i = 0; i < 10; i++) { [queue addOperationWithBlock:^{ NSLog(@"down %d %@",i,[NSThread currentThread]); }]; }
3. 全局操作队列
以下我们定义一个全局队列来调度全部的异步操作
@property (nonatomic, strong) NSOperationQueue *queue; //懒载入队列- (NSOperationQueue *)queue{ if (_queue == nil) { _queue = [[NSOperationQueue alloc] init]; } return _queue;}for (int i = 0; i < 10; i++){ [self.queue addOperationWithBlock:^{ NSLog(@"down %d %@",i,[NSThread currentThread]); }]; }
4. 监听操作完毕
[op1 setCompletionBlock:^{ NSLog(@"....."); }];
并发数
- 并发数:同一时候执⾏行的任务数.比方,同一时候开3个线程运行3个任务,并发数就是3
- 最大并发数:同一时间最多仅仅能运行的任务的个数。
最⼤大并发数的相关⽅方法
- (NSInteger)maxConcurrentOperationCount;- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
注意:
假设没有设置最大并发数。那么并发的个数是由系统内存和CPU决定的,可能内存多就会开多一点。内存少就开少一点。
num的值并不代表线程的个数,仅仅代表线程的ID。
最大并发数不要乱写(5以内)。不要开太多。一般以2~3为宜,由于尽管任务是在子线程进行处理的,可是cpu处理这些过多的子线程可能会影响UI,让UI变卡。
- (void)demo{ self.queue.maxConcurrentOperationCount = 3; for (int i=1; i<50; i++) { [self.queue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:3.0]; NSLog(@"====%@===%d",[NSThread currentThread],i); }]; [NSThread sleepForTimeInterval:1.0]; }}
不加最大并发数:
此时会创建非常多线程,线程数越多说明线程池更大了。
可是线程越多越耗资源,分配线程的时间也就越多。所以使用线程的时候要合适最好
GCD通常仅仅会开启5~6个线程
通过设置sleepForTimeInterval能够延迟线程运行时间,也能够降低线程数。可是一来于操作的运行时间。
加入最大并发数:
- 把操作加入到队列
[ self.queue addOperationWithBlock]
去线程池去取空暇的线程,假设没有就创建线程
把操作交给从线程池中取出的线程运行
运行完毕后,把线程再放回线程池中
反复2,3,4直到全部的操作都运行完
队列的取消,暂停和恢复
取消队列的全部操作
- (void)cancelAllOperations;
提⽰:
也能够调用NSOperation的
- (void)cancel;
⽅法取消单个操作
暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列- (BOOL)isSuspended; //当前状态
暂停和恢复的适用场合
在tableview界面,开线程下载远程的网络界面,对UI会有影响。使用户体验变差。那么这样的情况。就能够设置在用户操作UI(如滚动屏幕)的时候。暂停队列(不是取消队列),停止滚动的时候,恢复队列。
演示样例代码-摇奖机
#import "ViewController.h"@interface ViewController ()@property (weak, nonatomic) IBOutlet UILabel *lbl0;@property (weak, nonatomic) IBOutlet UILabel *lbl1;@property (weak, nonatomic) IBOutlet UILabel *lbl2;@property (weak, nonatomic) IBOutlet UIButton *btn;//全局队列@property (nonatomic, strong) NSOperationQueue *queue;- (IBAction)start:(UIButton *)sender;@end@implementation ViewController//队列懒载入-> 假设没有初始话queue, 则不会有队列生成,程序不会有响应- (NSOperationQueue *)queue{ if (_queue == nil) { _queue = [[NSOperationQueue alloc] init]; } return _queue;}- (void)viewDidLoad { [super viewDidLoad]; }- (IBAction)start:(UIButton *)sender { //首先要推断队列是否为空->推断队列是否是挂起状态时,并不会推断队列中是否有操作 if(self.queue.operationCount == 0) { //假设操作为空,说明运行摇奖,同一时候開始button变暂停 [self.queue addOperationWithBlock:^{ [self random]; }]; [self.btn setTitle:@"停止" forState:UIControlStateNormal]; //当前队列运行。不被挂起 self.queue.suspended = NO; } else if(self.queue.isSuspended) { //假设当前是挂起状态->開始队列->btn:暂停 self.queue.suspended = NO; [self.btn setTitle:@"停止" forState:UIControlStateNormal]; } else { //假设当前是运行状态->暂停队列->btn:開始 self.queue.suspended = YES; [self.btn setTitle:@"開始" forState:UIControlStateNormal]; }}- (void)random { //假设单签队列没有挂起 while (![NSOperationQueue currentQueue].isSuspended) { int num = arc4random_uniform(10); int num1 = arc4random_uniform(10); int num2 = arc4random_uniform(10); [NSThread sleepForTimeInterval:0.05]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.lbl0.text = [NSString stringWithFormat:@"%d",num]; self.lbl1.text = [NSString stringWithFormat:@"%d",num1]; self.lbl2.text = [NSString stringWithFormat:@"%d",num2]; }]; }}@end
操作优先级
设置NSOperation在queue中的优先级,能够改变操作的执⾏优先级
- (NSOperationQueuePriority)queuePriority;- (void)setQueuePriority:(NSOperationQueuePriority)p;
优先级的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
说明:优先级高的任务,调用的几率会更大。
操作依赖
NSOperation之间能够设置依赖来保证运行顺序。⽐如一定要让操作A运行完后,才干运行操作B:
[opB addDependency:opA];
能够在不同queue的NSOperation之间创建依赖关系
注意:
不能循环依赖(不能A依赖于B,B又依赖于A)
1. 模拟软件的部分升级
/*=======依赖关系========*//***模拟软件的部分升级:下载->解压->通知用户升级*///下载压缩包-> 操作NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载 %@",[NSThread currentThread]); }];//解压,拷贝到对应文件夹NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"解压 %@",[NSThread currentThread]); }];//通知用户NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"通知用户升级完毕 %@",[NSThread currentThread]); }];//设置操作的依赖关系[op2 addDependency:op1];[op3 addDependency:op2];//加入操作/***waitUntilFinished YES 等待全部的操作运行完毕 会堵塞窗口的运行*waitUntilFinished NO 不等待*/[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];NSLog(@"over");
循环依赖
发生循环依赖,程序不会死锁。界面也不会堵塞,操作不会运行
[op2 addDependency:op1]; [op3 addDependency:op2]; [op1 addDependency:op3];3. 依赖关系能够跨队列运行
[op2 addDependency:op1];[op3 addDependency:op2]; //子队列 [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];NSLog(@"over");[[NSOperationQueue mainQueue] addOperation:op3];
提示
任务加入的顺序并不能够决定运行顺序,运行的顺序取决于依赖。
使用Operation的目的就是为了让开发者不再关心线程。
操作的监听
能够监听一个操作的运行完毕:
- (void (^)(void))completionBlock;- (void)setCompletionBlock:(void (^)(void))block;
- (void)demo{ NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i=1; i<3; i++) { [NSThread sleepForTimeInterval:1.0]; NSLog(@"%@==download image = %d",[NSThread currentThread], i); } }]; [self.queue addOperation:op]; op.completionBlock =^{ NSLog(@"finishing download image, do other things"); }; NSLog(@"%@====I'm later than downloading image",[NSThread currentThread]);}
注意:
下载图片的操作在子线程中运行,op仅仅是加入到队列里了。至于什么时候运行须要等待对应的线程;
因此第三个输出出如今最前面。且在主线程中。
completionBlock中的操作要等待下载图片完毕。
GCD和NSOperation的比較
GCD
- GCD是iOS4.0推出的 ,主要针对多核cpu做了优化,是C语言的技术
- GCD是将任务(block)加入到队列(串行/并行/全局/主队列),而且以同步/异步的方式运行任务的函数
GCD提供了一些NSOperation不具备的功能
- 一次性运行
- 延迟运行
- 调度组
NSOperation
- NSOperation是iOS2.0推出的,iOS4之后重写了NSOperation
- NSOperation将操作(异步的任务)加入到队列(并发队列),就会运行指定操作的函数
NSOperation里提供的方便的操作 :
- 最大并发数
- 队列的暂停/继续
- 取消全部的操作
- 指定操作之间的依赖关系(GCD能够用同步实现)