iOS多线程(NSThread,GCD,NSOpertion)
最近看资料熟悉了一遍iOS多线程,就整理下来做个分享和记录(如有错误,请指正!)
进入主题
说起多线程,就要从进程和线程开始
进程:进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专门且受保护的内存空间内
线程:一个进程想要执行任务,必须得有线程(一个进程至少有一个线程),一个进程的所有任务都在线程中执行
什么是多线程?
1.一个进程中可以开启多条线程,每条线程可以并行执行不同的任务
2.多线程技术可以提高程序的执行效率
多线程的原理
1.同一时间,CPU只能处理一条线程,只有一条线程再工作
2.多线程并发执行,其实就是CPU快速的再多条线程间调度
3.如果CPU在调度线程的时间足够快,就造成了多线程并发执行的假象
多线程的优缺点
优点:
1.能适当的提高程序的执行效率
2.能适当的提高资源利用率(CPU,内存利用率)
缺点:
1.创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB),栈空间(子线程512KB,主线程1MB,也可以使用setStackSize设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的时间
2.如果开启大量的线程,会降低程序的性能
3.线程越多,CPU再调度线程上的开销就越大
4.程序设计更加复杂:比如线程间的通信,多线程的数据共享
多线程在iOS开发中的应用
什么是主线程
一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"
主线程的主要作用
1.显示/刷新UI界面
2.处理UI事件(比如点击事件,滚动事件,拖拽事件等)
主线程的使用注意
1.不要讲比较耗时的操作放到主线程,耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种"卡"的体验
2.如果将耗时操作放到主线程
image.png
3.如果将耗时操作放到子线程
image.png
为什么刷新UI的操作都要放到主线程?
OC在定义属性时,有nonatomic和atomic两种选择
1.atomic : 原子属性,为setter方法加锁(默认就是atomic)
2.nonatomic: 非原子属性,不会为setter方法加锁
atomic:实际上,原子属性内部会有一把自旋锁,同一时间,只有一个线程访问,保证数据安全,但是比较消耗性能,反映到用户面前,就是界面不顺滑,但是nonatomic在多线程下又是不安全的,会造成资源掠夺,数据错乱,所以考虑到用户体验和数据安全等问题,苹果给开发者规范,更新UI的操作都在放到主线程执行,同时,iOS中UIKit库的组件也都是非原子属性
iOS中多线程的实现方案
image2.png多线程存在什么隐患以及怎么解决?
1,资源共享
1.一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源(比如多个线程访问同一个对象,同一个变量,同一个文件)
2.当多个线程访问通一块资源时,很容易引发数据错乱和数据安全问题
解决方案:加锁(互斥锁)
image3.png假如两个售票员同时开始售票,怎么样保证仓库的余票是正确的,
1.如果不加锁,假如余票20张,售票员1卖了1张,剩余19张,同时售票员2也卖了1张,这个时候余票也是19张,这是不对的,其实只剩余18张
2.加锁,保证数据安全
售票员1买票的时候,售票员2不能买票,必须等待1结束售票,售票员2才能去售票,这时候,2去售票的时候,余票就是19张,这样就保证了数据安全(上图也同样能说明这个问题)
具体代码实现加锁
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
t1.name = @"seller1";
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
t2.name = @"seller2";
[t2 start];
}
- (void)sellTicket {
while (YES) {
//互斥锁:保证锁内的代码,同一时间只有一个线程执行
@synchronized (self) {
if (self.tickets > 0) {
self.tickets --;
NSLog(@"剩下%ld张票=====%@",(long)self.tickets,[NSThread currentThread]);
}else {
NSLog(@"票售空");
break;
}
}
}
}
互斥锁的使用前提:多条线程抢夺同一块资源
互斥锁的优缺点:
优点: 能有效防止因多线程抢夺资源造成的数据安全问题
缺点: 需要消耗大量的CPU资源
iOS中多线程的实现
pthread基于C,跨平台,可移植,但是难度大,而且对于iOS开发几乎不用,这里就不在介绍
1.iOS多线程开发-----NSThread(使用频率:偶尔使用)
NSThread是基于OC,使用更加面向对象,可直接操作线程对象
1个NSThread对象就代表一条线程
NSThread创建线程的3个方法
//创建一个线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:@"thread"];
//线程的名称(崩溃的时候可以查找崩溃原因)
thread.name = @"testThread";
//线程优先级(0.0-1.0,默认0.5,1优先级最高,优先级不是绝对的,只是保证CPU的调度可能性,不能根据优先级来决定线程的执行顺序)
thread.threadPriority = 1;
//线程一启动,就会在线程thread中执行run方法
[thread start];
//创建一个线程
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:@"thread"];
//创建一个线程
[self performSelectorInBackground:@selector(test) withObject:nil];
NSThread的其他用法
//获取主线程
[NSThread mainThread];
//判断是否为主线程
BOOL isMainThread = [thread isMainThread];
//获取当前线程
NSThread *currentThread = [NSThread currentThread];
//阻塞线程
[NSThread sleepForTimeInterval:2.0];
//强行终止线程
[NSThread exit];
线程间的通信(一个线程中执行完任务后,转到另一个线程执行任务)
//NO:不等待,主线程执行,子线程继续执行
//YES: 等待主线程完成,子线程才会继续执行
//转到另外一个线程
[self performSelector:@selector(setDownloadImage) onThread:thread withObject:nil waitUntilDone:NO];
//回到主线程
[self performSelectorOnMainThread:@selector(setDownloadImage) withObject:nil waitUntilDone:NO];
最简单的线程通信的例子(子线程下载图片,下载完成,回到主线程刷新UI)
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.imageView];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
[thread start];
}
- (void)downloadImage {
//耗时操作放到子线程
NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/a8ec8a13632762d0b09949edadec08fa513dc639.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image = [UIImage imageWithData:data];
//NO:不等待,主线程执行,子线程继续执行
//YES: 等待主线程完成,子线程才会继续执行
[self performSelectorOnMainThread:@selector(setDownloadImage) withObject:nil waitUntilDone:NO];
}
- (void)setDownloadImage {
self.imageView.image = self.image;
}
iOS多线程开发-----GCD
什么是GCD
GCD:Grand Central Dispatch
GCDGCD是苹果公司为多核的并行运算提出的解决方案,也是基于C语言,提供了非常强大的函数
(!!!看资料说多线程的其实只有pthread和NSThread,GCD和NSOpertion只是提供的一个多核并行运算方案,不太明白,待验证)
GCD的优势
1.GCD是苹果公司为多核的并行运算提出的解决方案
2.GCD会自动利用更多的CPU内核
3.GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
4.程序员只需要告诉GCD想要执行什么任务,不需要写任何线程管理的代码
GCD的两个核心概念
1.任务(执行什么操作)
2.队列(用来存放什么任务)
GCD的使用
1.定制任务
2.将任务添加到队列
1.GCD会自动将队列中的任务取出,放到对应的线程中执行
2.任务的取出遵循FIFO原则,即 先进先出
使用GCD钱要先清楚四个概念
同步
只能在当前线程执行任务,不具备开启新线程的能力
异步
可以在新的线程中执行任务,具备开启新线程的能力
串行队列
让任务一个接一个的执行(一个任务执行完毕,再执行下一个)
并发(并行)队列
可以让多个任务并发执行(自动开启多个线程同时执行任务),并发功能只有在异步函数下有效
GCD的各种任务在队列的执行情况
1.串行队列执行同步任务(不会开辟子线程,同步执行)
//串行队列执行同步任务
dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 16:55:00.674622+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======0
2018-11-14 16:55:00.674815+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======1
2018-11-14 16:55:00.674950+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======2
2018-11-14 16:55:00.675064+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======3
2018-11-14 16:55:00.675171+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======4
2018-11-14 16:55:00.675295+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======5
2018-11-14 16:55:00.675413+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======6
2018-11-14 16:55:00.675538+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======7
2018-11-14 16:55:00.675652+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======8
2018-11-14 16:55:00.675757+0800 GCD[57088:1947563] <NSThread: 0x60000022e940>{number = 1, name = main}=======9
2.串行队列执行异步任务(会开辟一条子线程,同步执行)
//串行队列执行异步任务
dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 17:08:29.331567+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======0
2018-11-14 17:08:29.332030+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======1
2018-11-14 17:08:29.332268+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======2
2018-11-14 17:08:29.332546+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======3
2018-11-14 17:08:29.333324+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======4
2018-11-14 17:08:29.333495+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======5
2018-11-14 17:08:29.333670+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======6
2018-11-14 17:08:29.333815+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======7
2018-11-14 17:08:29.333944+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======8
2018-11-14 17:08:29.334566+0800 GCD[57320:1971811] <NSThread: 0x600003fd94c0>{number = 3, name = (null)}=======9
3.并发队列执行同步任务(不会开辟新线程,同步执行)
//并发队列执行同步任务
dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 17:17:28.179815+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======0
2018-11-14 17:17:28.180028+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======1
2018-11-14 17:17:28.180179+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======2
2018-11-14 17:17:28.180311+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======3
2018-11-14 17:17:28.180431+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======4
2018-11-14 17:17:28.180564+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======5
2018-11-14 17:17:28.180688+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======6
2018-11-14 17:17:28.180802+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======7
2018-11-14 17:17:28.180915+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======8
2018-11-14 17:17:28.181028+0800 GCD[57460:1986502] <NSThread: 0x600001b36d00>{number = 1, name = main}=======9
4.并发队列执行异步任务(开辟新线程,异步执行)
//并发队列执行异步任务
dispatch_queue_t q = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 17:19:21.958412+0800 GCD[57493:1990212] <NSThread: 0x600002b21240>{number = 3, name = (null)}=======0
2018-11-14 17:19:21.958414+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======2
2018-11-14 17:19:21.958415+0800 GCD[57493:1990209] <NSThread: 0x600002b21140>{number = 5, name = (null)}=======3
2018-11-14 17:19:21.958472+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======1
2018-11-14 17:19:21.958700+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======5
2018-11-14 17:19:21.958724+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======7
2018-11-14 17:19:21.958744+0800 GCD[57493:1990209] <NSThread: 0x600002b21140>{number = 5, name = (null)}=======4
2018-11-14 17:19:21.958757+0800 GCD[57493:1990212] <NSThread: 0x600002b21240>{number = 3, name = (null)}=======6
2018-11-14 17:19:21.958859+0800 GCD[57493:1990211] <NSThread: 0x600002b2fc00>{number = 6, name = (null)}=======8
2018-11-14 17:19:21.958859+0800 GCD[57493:1990210] <NSThread: 0x600002b2fb40>{number = 4, name = (null)}=======9
5.全局队列执行同步任务(不会开辟新线程,同步执行)
//全局队列执行同步任务
/*
ios8 服务质量
QOS_CLASS_USER_INTERACTIVE 用户交互(希望线程快速执行,不要放一些耗时操作)
QOS_CLASS_USER_INITIATED (用户需要,不要放一些耗时操作)
QOS_CLASS_DEFAULT 默认
QOS_CLASS_UTILITY 使用工具(耗时操作)
QOS_CLASS_BACKGROUND 后台
QOS_CLASS_UNSPECIFIED 没有指定优先级
ios7 调度优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
不要选择BACKGROUND 执行慢到令人发指
*/
dispatch_queue_t q = dispatch_get_global_queue(0, 0);//0 代表默认优先级 0 保留参数,
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 17:57:17.473335+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======0
2018-11-14 17:57:17.473562+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======1
2018-11-14 17:57:17.473666+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======2
2018-11-14 17:57:17.473827+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======3
2018-11-14 17:57:17.473970+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======4
2018-11-14 17:57:17.474112+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======5
2018-11-14 17:57:17.474248+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======6
2018-11-14 17:57:17.474385+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======7
2018-11-14 17:57:17.474522+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======8
2018-11-14 17:57:17.474654+0800 GCD[58138:2042297] <NSThread: 0x600001f1e940>{number = 1, name = main}=======9
6.全局队列执行异步任务(开辟新线程,异步执行)
//全局队列执行异步任务
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@=======%d",[NSThread currentThread],i);
});
}
2018-11-14 17:55:44.488898+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======1
2018-11-14 17:55:44.488904+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======0
2018-11-14 17:55:44.488931+0800 GCD[58100:2039076] <NSThread: 0x600000d614c0>{number = 6, name = (null)}=======2
2018-11-14 17:55:44.488934+0800 GCD[58100:2039082] <NSThread: 0x600000d6ab00>{number = 5, name = (null)}=======3
2018-11-14 17:55:44.489128+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======5
2018-11-14 17:55:44.489136+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======4
2018-11-14 17:55:44.489183+0800 GCD[58100:2039082] <NSThread: 0x600000d6ab00>{number = 5, name = (null)}=======7
2018-11-14 17:55:44.489220+0800 GCD[58100:2039076] <NSThread: 0x600000d614c0>{number = 6, name = (null)}=======6
2018-11-14 17:55:44.489254+0800 GCD[58100:2039075] <NSThread: 0x600000d61400>{number = 4, name = (null)}=======8
2018-11-14 17:55:44.489314+0800 GCD[58100:2039074] <NSThread: 0x600000d6aa00>{number = 3, name = (null)}=======9
死锁(在主线程执行同步操作)
主线程是串行的,线性往下执行,在执行某一个任务的时候线程被阻塞了,等待这个任务执行完再往下执行,而这个任务(dispatch_sync)在执行时,又要求阻塞主线程,等待主线程的任务执行完再去执行block,互相等待,从而导致了互相的阻塞,也就是死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行");
});
NSLog(@"执行结束");
总结:
同步执行:不论串行还是并行,都不会开辟子线程,都是同步执行
异步执行:会开辟子线程(开辟线程的多少,由CPU决定),(串行:顺序执行,并行:异步执行)
开不开线程,取决于执行任务的函数,同步不开,异步开
开几条线程,取决于队列,串行一条,并行多条(异步) 全局队列等同并发队列
GCD的其他应用
单例(一次执行)
static dispatch_once_t oncetoken;
dispatch_once(&oncetoken, ^{//代码只会被执行一次
NSLog(@"%@",[NSThread currentThread]);
});
延时操作
//延时3秒执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
调度组(被添加到调度组的队列,都执行结束,会通知调度组,执行调度结束的操作)
//1.队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.调度组
dispatch_group_t g = dispatch_group_create();
//3.添加任务,将队列添加到调度组
dispatch_group_async(g, q, ^{
NSLog(@"1111");
});
dispatch_group_async(g, q, ^{
NSLog(@"2222");
});
dispatch_group_async(g, q, ^{
NSLog(@"3333");
});
dispatch_group_async(g, q, ^{
NSLog(@"4444");
});
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
NSLog(@"执行结束");
});
2018-11-15 17:37:52.263300+0800 GCD[69146:2964535] 1111
2018-11-15 17:37:52.263331+0800 GCD[69146:2964882] 2222
2018-11-15 17:37:52.263408+0800 GCD[69146:2964883] 3333
2018-11-15 17:37:52.263439+0800 GCD[69146:2964884] 4444
2018-11-15 17:37:52.263765+0800 GCD[69146:2964437] 执行结束
注意!!!
我们在工作开发中假如首页有5个接口,需要5个接口全部请求结束,再去刷新UI,能不能用上面的方式来实现?
答案是不可以,因为group的设计就是为了方便我们执行完一系列的任务之后再执行其他的任务,但是不能忽视的是,这里的任务是有要求的,这里的任务必须要是同步执行的!!如果任务是异步的,group只会执行完任务里面异步之前的代码以及分发异步任务就返回了!!也就代表分发group的当前这个任务完成了!但事实却是这个任务的一部分子任务在其他线程执行了,而且不一定已执行结束返回。我们AFN请求的操作都是异步的,所以这个是不能实现我们的需求,这时候需要dispatch_group_enter(); dispatch_group_leave();来实现
dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。
dispatch_enter和dispatch_leave要成对出现,否则奔溃。
//调度组
dispatch_group_t g = dispatch_group_create();
//模拟网络请求1
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"接口1请求完成");
dispatch_group_leave(g);
});
});
//模拟网络请求2
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"接口2请求完成");
dispatch_group_leave(g);
});
});
//模拟网络请求3
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"接口3请求完成");
dispatch_group_leave(g);
});
});
//模拟网络请求4
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"接口4请求完成");
dispatch_group_leave(g);
});
});
//模拟网络请求5
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"接口5请求完成");
dispatch_group_leave(g);
});
});
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
NSLog(@"执行结束");
});
2018-11-15 17:50:23.102497+0800 GCD[69395:2984377] 接口1请求完成
2018-11-15 17:50:23.102905+0800 GCD[69395:2984377] 接口2请求完成
2018-11-15 17:50:23.103377+0800 GCD[69395:2984377] 接口3请求完成
2018-11-15 17:50:23.103921+0800 GCD[69395:2984377] 接口4请求完成
2018-11-15 17:50:23.104095+0800 GCD[69395:2984377] 接口5请求完成
2018-11-15 17:50:23.104270+0800 GCD[69395:2984377] 执行结束
NSOpertion
NSOpertion也是苹果公司为多核的并行运算提出的解决方案
NSOpertion是一个抽象类,我们使用需要用他的子类NSInvocationOperation和NSBlockOperation
NSOpertion是对GCD的封装 默认是并发队列,异步操作
NSOpertion的核心概念是将操作添加到队列(对比GCD:将任务添加到队列)
对比GCD
GCD是 ios4.0 推出,主要针对多核处理器来优化的并发技术,C语言
1.GCD:将任务添加到队列,并且要指定任务的同步或者异步
2.线程间通信:dispatch_get_main_queue() 3.提供了一些NSOpertion不具备的功能(一次执行,延时操作)
NSOpertion 在ios2.0,苹果推出GCD之后,对NSOpertion进行了重写
NSOpertion:将操作(异步执行的任务)添加到并发队列,就会立刻执行
提供GCD不具备的功能:(最大并发数,队列暂停,继续,取消所有线程,线程的依赖)
操作(会在当前线程执行)
- (void)demo1 {
//操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
[op start];//会在当前线程执行
}
- (void)test {
NSLog(@"%@",[NSThread currentThread]);
}
2018-11-15 17:32:15.237200+0800 NSOpertion[69033:2952177] <NSThread: 0x600003360900>{number = 1, name = main}
队列(操作添加到队列,默认开始异步并发) NSInvocationOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperation:op];
2018-11-15 14:52:12.420077+0800 NSOpertion[66330:2684747] <NSThread: 0x6000039afdc0>{number = 3, name = (null)}
NSBlockOperation
NSOperationQueue *q = [[NSOperationQueue alloc] init];
//异步,并发
for (int i = 0; i < 5; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[q addOperation:op];
}
//简写方式
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[q addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
}
2018-11-15 14:54:20.747733+0800 NSOpertion[66399:2690054] <NSThread: 0x600003e65bc0>{number = 3, name = (null)}
2018-11-15 14:54:20.747910+0800 NSOpertion[66399:2690056] <NSThread: 0x600003e66240>{number = 4, name = (null)}
2018-11-15 14:54:20.747927+0800 NSOpertion[66399:2690064] <NSThread: 0x600003e66200>{number = 5, name = (null)}
2018-11-15 14:54:20.747959+0800 NSOpertion[66399:2690055] <NSThread: 0x600003e66300>{number = 6, name = (null)}
2018-11-15 14:54:20.748638+0800 NSOpertion[66399:2690054] <NSThread: 0x600003e65bc0>{number = 3, name = (null)}
最大并发数:maxConcurrentOperationCount
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
NSOperation 操作依赖
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
当然,我们经常用到的还是添加依赖操作。现在考虑这样的需求,比如说有 A、B 两个操作,其中 A 执行完操作,B 才能执行操作。
如果使用依赖来处理的话,那么就需要让操作 B 依赖于操作 A
/**
* 操作依赖
* 使用方法:addDependency:
*/
- (void)addDependency {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.添加依赖
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
// 4.添加操作到队列中
[queue addOperation:op1];
[queue addOperation:op2];
}
可以看到:通过添加操作依赖,无论运行几次,其结果都是 op1 先执行,op2 后执行