GCD相关
什么是GCD?
全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
纯C语言,提供了非常多强大的函数
GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD中有2个核心概念
(1)任务:执行什么操作
(2)队列:用来存放任务
GCD的使用就2个步骤
(1)定制任务
(2)确定想做的事情
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出
执行任务
1.GCD中有2个用来执行任务的函数
说明:把右边的参数(任务)提交给左边的参数(队列)进行执行。
(1)用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
参数说明:
queue:队列
block:任务
(2)用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步和异步的区别
同步:在当前线程中执行
异步:在另一条线程中执行
队列
队列的类型
GCD的队列可以分为2大类型
(1)并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);
(2)串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);
全局并发队列
GCD默认提供了全局并发队列,供整个应用使用,可以无需手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
//参数1:队列的优先级
//参数2:此参数暂时无用,为0即可
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);
获得全局并发队列
/*
The global concurrent queues may still be identified by their priority,
* which map to the following QOS classes:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED //优先级高
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT //优先级中等(默认)
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY //优先级低
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND //优先级在后台,最低
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
主队列(跟主线程相关的队列)
- 主队列是GCD自带的一种特殊串行队列
- 放在主队列中的任务,都会放到主线程中执行
- 使用dispatch_get_main_queue()获得主队列
补充说明
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行决定了任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
//异步函数+并发队列:会开启多条线程,队列中的任务是异步执行
- (void)asyncConcurrent
{
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
/* 执行的顺序由GCD决定,开机条线程也有GCD决定
2016-11-30 17:51:31.516978 GCDQueueTest[1698:510701] download3---<NSThread: 0x17406a100>{number = 3, name = (null)}
2016-11-30 17:51:31.517236 GCDQueueTest[1698:510701] download2---<NSThread: 0x17406a100>{number = 4, name = (null)}
2016-11-30 17:51:31.517404 GCDQueueTest[1698:510701] download2---<NSThread: 0x17406a100>{number = 2, name = (null)}
*/
//异步函数+串行队列:会开启一条线程,队列中的任务是串行执行的
- (void)asyncSerial
{
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
/*
2016-11-30 17:48:38.858472 GCDQueueTest[1689:509194] download1---<NSThread: 0x17406d480>{number = 3, name = (null)}
2016-11-30 17:48:38.858699 GCDQueueTest[1689:509194] download2---<NSThread: 0x17406d480>{number = 3, name = (null)}
2016-11-30 17:48:38.858783 GCDQueueTest[1689:509194] download3---<NSThread: 0x17406d480>{number = 3, name = (null)}
*/
//同步函数+并行队列:不会开线程,队列中的任务是串行执行的
- (void)syncConcurrent
{
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
/*
2016-11-30 17:47:35.205883 GCDQueueTest[1686:508612] download1---<NSThread: 0x17406ab80>{number = 1, name = main}
2016-11-30 17:47:35.206162 GCDQueueTest[1686:508612] download2---<NSThread: 0x17406ab80>{number = 1, name = main}
2016-11-30 17:47:35.206326 GCDQueueTest[1686:508612] download3---<NSThread: 0x17406ab80>{number = 1, name = main}
*/
//同步函数+串行队列:不会开线程,队列中的任务是串行执行的
- (void)syncSerial
{
dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
/*输出
2016-11-30 17:46:15.371174 GCDQueueTest[1684:508139] download1---<NSThread: 0x17006f680>{number = 1, name = main}
2016-11-30 17:46:15.371319 GCDQueueTest[1684:508139] download2---<NSThread: 0x17006f680>{number = 1, name = main}
2016-11-30 17:46:15.371392 GCDQueueTest[1684:508139] download3---<NSThread: 0x17006f680>{number = 1, name = main}
*/
//异步函数 + 主队列,虽然具备开子线程的条件,但主队列必须要在主线程中执行,所以没有必要开线程
- (void)asyncMain
{
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
/*
2016-11-30 23:29:54.734 GCDQueueTest[5098:181614] download1---<NSThread: 0x6000000749c0>{number = 1, name = main}
2016-11-30 23:29:54.736 GCDQueueTest[5098:181614] download2---<NSThread: 0x6000000749c0>{number = 1, name = main}
2016-11-30 23:29:54.743 GCDQueueTest[5098:181614] download3---<NSThread: 0x6000000749c0>{number = 1, name = main}
*/
//同步函数 + 主队列 造成死锁。
//主队列的特点:如果主队列发现当前主线程中有任务正在执行,主队列则会停止调用队列中的任务,直到主队列空闲为止
- (void)syncMain
{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"start----------");
dispatch_sync(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3---%@",[NSThread currentThread]);
});
}
//但如果上面函数放在一个子线程中执行,不会造成死锁
//比如:NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(syncMain) object:nil];
//[thread start];
/*
2016-11-30 23:33:06.238 GCDQueueTest[5297:188125] download1---<NSThread: 0x608000068080>{number = 1, name = main}
2016-11-30 23:33:06.239 GCDQueueTest[5297:188125] download2---<NSThread: 0x608000068080>{number = 1, name = main}
2016-11-30 23:33:06.239 GCDQueueTest[5297:188125] download3---<NSThread: 0x608000068080>{number = 1, name = main}
*/
总结上述
小结
说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
同步函数
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
补充:
凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。 GCD的数据类型在ARC的环境下不需要再做release。 CF(core Foundation)的数据类型在ARC环境下还是需要做release。 异步函数具备开线程的能力,但不一定会开线程
GCD延迟执行的方法以及其他延迟执行的方法
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self delay];
}
- (void)delay
{
NSLog(@"-----start-----");
//1.第一种延迟执行的方法
//[self performSelector:@selector(task) withObject:nil afterDelay:2.0];
//2.第二种延迟执行的方法
//[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
//3.GCD的延时执行的方法
//参数1:DISPATCH_TIME_NOW 从现在开始计算时间
//参数2:延迟的时间 乘以 GCD时间单位(纳秒)
//参数3:执行的队列,比如 dispatch_get_main_queue()
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"GCD--------%@",[NSThread currentThread]);
});
}
- (void)task
{
NSLog(@"task--------%@",[NSThread currentThread]);
}
@end
GCD一次性代码
需求:点击控制器只有第一次点击的时候才打印。
- (void)once
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"这里执行一次性的代码");
});
}
缺点:这是一个对象方法,如果又创建一个新的控制器,那么打印代码又会执行,因为每个新创建的控制器都有自己的布尔类型,且新创建的默认为NO,因此不能保证改行代码在整个程序中只打印一次。
注意,懒加载中不能使用一次性代码
GCD栅栏函数
什么是dispatch_barrier_async函数
毫无疑问,dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
dispatch_barrier_async函数的作用
1.实现高效率的数据库访问和文件访问
2.避免数据竞争
注意:栅栏函数对全局并发队列无效,只能用于自己创建的并发队列
dispatch_barrier_async实例
- (void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---download2---%@",[NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"++++++++++++++++barrier+++++++++++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"---download3---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---download4---%@",[NSThread currentThread]);
});
}
输出结果:1 2 --> barrier -->3 4 其中12 与 34 由于并行处理先后顺序不定
GCD快速迭代函数
dispathc_apply 是dispatch_sync 和dispatch_group的关联API.它以指定的次数将指定的Block加入到指定的队列中。并等待队列中操作全部完成.
- (void)apply
{
//dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t))
//参数1:迭代的次数
//参数2:并发队列
//参数3:index索引
dispatch_apply(100000, dispatch_get_global_queue(0,0), ^(size_t index) {
NSLog(@"%zd-----%@",index,[NSThread currentThread]);
});
}
输出 index 顺序不确定,因为它是并行执行的(dispatch_get_global_queue是并行队列),但是done是在以上拷贝操作完成后才会执行,因此,它一般都是放在dispatch_async里面(异步)。实际上,这里 dispatch_apply如果换成串行队列上,则会依次输出index,但这样违背了我们想并行提高执行效率的初衷。
一个文件剪贴的操作用GCD方式实现的实例
- (void)moveFiles
{
NSString *fromFolder = @"/Users/zhangfan/Desktop/from";
NSString *toFolder = @"/Users/zhangfan/Desktop/to";
NSArray *subPath = [[NSFileManager defaultManager] subpathsAtPath:fromFolder];
NSInteger count = subPath.count;
// 如果使用for循环,全部使用主线程完成,这样如果操作大文件会导致整个程序一定时间内卡死
// for(int i = 0; i < count; i++)
// {
// NSString *fromFullPath = [fromFolder stringByAppendingPathComponent:subPath[i]];
// NSString *toFullPath = [toFolder stringByAppendingPathComponent:subPath[i]];
// [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
// NSLog(@"已经将文件从%@移动到%@",fromFullPath,toFullPath);
// }
//如果交给GCD处理,会自动的分配子线程去处理
dispatch_apply(count, dispatch_get_global_queue(0,0), ^(size_t index) {
NSString *fromFullPath = [fromFolder stringByAppendingPathComponent:subPath[index]];
NSString *toFullPath = [toFolder stringByAppendingPathComponent:subPath[index]];
[[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
NSLog(@"已经将文件从%@移动到%@",fromFullPath,toFullPath);
});
}
GCD的队列组Group以及监听Group
有这么1种需求:
首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作
如果想要快速高效地实现上述需求,可以考虑用队列组
- (void)group
{
//获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建一个队列组
dispatch_group_t gruop = dispatch_group_create();
dispatch_group_async(gruop, queue, ^{
NSLog(@"1---%@---",[NSThread currentThread]);
});
//执行一个耗时的异步操作
dispatch_group_async(gruop, queue, ^{
NSLog(@"2---%@---",[NSThread currentThread]);
});
//执行一个耗时的异步操作
dispatch_group_async(gruop, queue, ^{
NSLog(@"3---%@---",[NSThread currentThread]);
});
//这里监听group完成的情况,完成后做什么事情
//这个方法是异步的,一定会等到group中最后一个任务执行完毕才会执行
dispatch_group_notify(gruop, queue, ^{
NSLog(@"已完成group中所有任务");
});
}
另外一种队列组的写法
- (void)group2
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t gruop = dispatch_group_create();
//dispatch_group_enter方法将进入群组,该方法后的异步任务都会放入队列组的监听范围
//dispatch_group_enter方法必须和dispatch_group_leave配对使用
dispatch_async(queue, ^{
NSLog(@"1---%@---",[NSThread currentThread]);
dispatch_group_leave(gruop);
});
dispatch_group_enter(gruop);
dispatch_async(queue, ^{
NSLog(@"2---%@---",[NSThread currentThread]);
dispatch_group_leave(gruop);
});
dispatch_group_enter(gruop);
dispatch_async(queue, ^{
NSLog(@"3---%@---",[NSThread currentThread]);
dispatch_group_leave(gruop);
});
dispatch_group_notify(gruop, queue, ^{
NSLog(@"已完成group中所有任务");
});
}
关于组拦截的方法
//不会造成阻塞,方法本身是异步的
dispatch_group_notify(gruop, queue, ^{
NSLog(@"已完成group中所有任务");
});
//会阻塞,直到group中所有的任务执行完,才会往后执行方法
dispatch_group_wait(gruop, DISPATCH_TIME_FOREVER);
NSLog(@"----end----");
一个使用GCD多线程队列组实现的下载2张图片,并且拼接成一张图片的实例
- (void)combineImages
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"download1-----%@",[NSThread currentThread]);
NSURL *url = [NSURL URLWithString:@"http://easyread.ph.126.net/JT_gINsEoeyrXZF-nMyjPw==/7917057665058273673.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image1 = [UIImage imageWithData:data];
});
dispatch_group_async(group, queue, ^{
NSLog(@"download2-----%@",[NSThread currentThread]);
NSURL *url = [NSURL URLWithString:@"http://i1.hdslb.com/video/3f/3fbfa4d263b53926a5c4f5c8c9056260.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.image2 = [UIImage imageWithData:data];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"combine images-----%@",[NSThread currentThread]);
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndPDFContext();
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Show image -----%@",[NSThread currentThread]);
self.imageView.image = image;
});
});
}
GCD中末尾带_f的函数
- (void)functionWithF
{
/*
* 参数1:队列
* 参数2:参数3要传的函数需要带的参数,无参数为NULL即可
* 参数3:要传的函数
*/
dispatch_async_f(<#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>);
}
//dispatch_async_f参数3调用函数的格式:typedef void (*dispatch_function_t)(void *_Nullable);
void task(void *param)
{
}
GCD中的定时器
@interface ViewController ()
@property(strong,nonatomic)dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self GCDTimer];
}
- (void)GCDTimer
{
//1.创建GCD中的定时器
/*
* 参数1:source的类型DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
* 参数2:描述信息,线程ID
* 参数3:更详细的描述信息
* 参数4:队列,决定GCD定时器在哪个任务中执行
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
//2.设定定时器(起始时间|间隔时间|精准度)
/*
* 参数1:定时器对象
* 参数2:起始时间,DISPATCH_TIME_NOW 从现在开始计时
* 参数3:间隔时间,GCD中的时间单位为纳秒(NSEC_PER_SEC)
* 参数4:精准度,0表示绝对精准
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.设置定时器执行的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD---%@",[NSThread currentThread]);
});
//4.启动执行
dispatch_resume(timer);
self.timer = timer;//设置为强引用才能工作,局部变量则会被释放
}
@end
GCD的定时器的优点:
- 不会受RunLoop影响
- 精准度高