本教程是一个合集,涉及到的目录结构:
基础知识总结
Block基础知识
GCD实战
CoreGraphics & ImageIO实战
CoreAnimation实战
Grand Central Dispatch(GCD)概要
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
先来个例子:
dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL);
dispatch_async(queue,^ {
/*
1. 执行图片数据下载
2. 将下载好的数据流转换成UIImage
*/
dispatch_async(dispatch_get_main_queue(),^{
/*
将转换后的UIImage绑定到UIImageView是
*/
});
}
特别说明
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行决定了任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
各种队列执行效果对比
类型 | 主队列 | 全局并发队列 | 手动创建串行队列 |
---|---|---|---|
同步(Sync) | 没有开启新线程;串行执行任务 | 没有开启新线程;串行执行任务 | 没有开启新线程;串行执行任务 |
异步(Async) | 没有开启新线程;串行执行任务 | 有开启新线程;并发执行任务 | 有开启新线程;串行执行任务 |
创建Dispatch Queue
- 通过API生成
- 同步队列
- dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL);
- dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",DISPATCH_QUEUE_SERIAL);
- 异步队列
- dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",DISPATCH_QUEUE_CONCURRENT);
- 同步队列
- 获取系统标准提供
- Main Dispatch Queue(同步)
- dispatch_queue_t queue = dispatch_get_main_queue();
- Global Dispatch Queue(异步)
- 高优先级
- dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)
- 默认优先级
- dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
- 低优先级
- dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0)
- 后台优先级
- dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0)
- 高优先级
- Main Dispatch Queue(同步)
对比表
名称 | Dispatch Queue的种类 | 说明 |
---|---|---|
Main Dispatch Queue | Serial Dispatch Queue | 主线程执行 |
Global Dispatch Queue(High Priority) | Concurrent Dispatch queue | 执行优先级:高(最高优先) |
Global Dispatch Queue(Default Priority) | Concurrent Dispatch queue | 执行优先级:默认 |
Global Dispatch Queue(Low Priority) | Concurrent Dispatch queue | 执行优先级:低 |
Global Dispatch Queue(Background Priority) | Concurrent Dispatch queue | 执行优先级:后台 |
dispatch_set_target_queue
dispatch_queue_create函数生成的Dispatch Queue不管是Serial Queue还是Concurrent Queue都是Default Priority,如果需要更改优先级,则需要使用dispatch_set_target_queue
使用方法:
dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_set_target_queue(queue,highPriorityQueue);
如果多个Dispatch Queue(例如1个Concurrent Dispatch Queue、2个Serial Dispatch Queue,或都是Serial Dispatch Queue(就算都是Serial Dispatch Queue,理论上对所有Queue来说也应该是并行))用dispatch_set_target_queue函数指定为同一个Serial Dispatch Queue,那么原本应该是并行执行的将变成串行执行
dispatch_after
经常会有这样的场景,想在3秒后执行处理,这个需求可使用dispatch_after函数来实现。
使用方法:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC);
dispatch_after(time,dispatch_get_main_queue(),^{
/*
3秒后自动执行任务
*/
});
需要注意的是,dispatch_after函数并不是在3秒后执行处理,而只是在3秒后追加处理到Dispatch Queue。上述代码中的Block最快在3秒后执行,最慢在3秒+1/60秒后执行,如果Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
创建dispatch_time_t
除了dispatch_time(DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC)
函数可创建dispatch_time_t
外,另外也可通过dispatch_walltime(const struct timespec *when, int64_t delta)
生成!
- dispatch_time通常用于计算相对时间
- dispatch_walltime通常用于计算觉得时间
dispatch_time_t getDispatchTimeByNSDate(NSDate *date) {
NSTimeInterval interval;
double second,subsecond;
struct timespec time;
dispatch_time_t milestone; /**< Dispatch time*/
interval = [date timeIntervalSinceInterval970];
subsecond = modf(interval,&second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time,0);
return milestone;
}
Dispatch Group
当追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这时可使用Dispatch Group。
使用dispatch_group_notify
使用例子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{ // 注意区别于dispatch_async,但是它们的用处是一样的
NSLog(@"block1");
});
dispatch_group_async(group,queue,^{
NSLog(@"block2");
});
dispatch_group_async(group,queue,^{
NSLog(@"block3");
});
dispatch_group_notify(group,dispatch_get_main_queue(),^{
NSLog(@"All finished.");
});
该源代码的执行结果为(Concurrent Dispatch Queue):
- block2
- block3
- block1
- All finshed.
因为是向Global Dispatch Queue(即Concurrent Dispatch Queue)追加处理,所以结果表现为多个线程并行处理。
使用dispatch_group_wait
使用例子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{ // 注意区别于dispatch_async,但是它们的用处是一样的
NSLog(@"block1");
});
dispatch_group_async(group,queue,^{
NSLog(@"block2");
});
dispatch_group_async(group,queue,^{
NSLog(@"block3");
});
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
如果需要检测是否已经执行完毕,则可通过dispatch_group_wait的返回值进行检测
使用如下:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if (0 == result) {
/*
属于Dispatch Group的全部处理执行结束
*/
} else {
/*
属于Dispatch Group的某一个处理还在执行中
*/
}
上述的time出现了2种情况:
- DISPTACH_TIME_NOW
- DISPTACH_TIME_FOREVER
他们具体的含义得好好说一下:如果给定的是DISPATCH_TIME_NOW,那么wait函数不用任何等待,马上返回。这是有可能返回值是1(也就是未执行完block),然后RunLoop再循环一次的时候,我们可以再判定返回值,这时有可能就是0(全部执行完毕)了;如果给定的是DISPTACH_TIME_FOREVER,那么执行dispatch_group_wait函数的现在的线程(当前线程)停止。一直等,直到Dispatch Group的处理全部执行完毕。当然除了DISPATCH_TIME_NOW或DISPATCH_TIME_FOREVER之外,我们也可指定某个wait时间
使用Dispatch Group进行“打包”
可使用dispatch_group_enter()和dispatch_group_leave()进行在区间类进行数据“打包”。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//Enter group
dispatch_group_enter(group);
[manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Deal with result...
//Leave group
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//Deal with error...
//Leave group
dispatch_group_leave(group);
}];
//More request...
使用dispatch_group_enter,dispatch_group_leave就可以方便的将一系列网络请求“打包”起来~
如果没有什么特别场景,建议使用dispatch_group_notify,因为可以简化源代码!
dispatch_barrier_async
一般来说,为了高效率的进行访问数据库或文件时,read处理会追加到Concurrent Dispatch Queue中,write处理在任一个read处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在write处理结束之前,read处理不可执行)
使用方法:
dispatch_async(queue,blk0_reading);
dispatch_async(queue,blk1_reading);
dispatch_async(queue,blk2_reading);
dispatch_async(queue,blk3_reading);
dispatch_barrier_async(queue,blk_writing);
dispatch_async(queue,blk4_reading);
dispatch_async(queue,blk5_reading);
dispatch_async(queue,blk6_reading);
dispatch_async(queue,blk7_reading);
上述代码执行过程为:
blk2_reading
blk1_reading
blk0_reading
blk3_reading
blk_writing -- 由dispatch_barrier_async追加的处理
blk5_reading
blk6_reading
blk4_reading
blk7_reading
在dispatch_barrier_async追加writng操作前,总共有4个block需要执行;在之后,总共也有4个block需要执行!从执行结果,我们不难发现,在writing之前的还是并行的处理,当碰到dispatch_barrier_async之后,整个流程就感觉变成了Serial Dispatch Queue(也可认为是等待dispatch_barrier_async执行完)。
特别说明:barrier的中文翻译为:障碍物、屏障
dispatch_async和dispatch_sync对比
前面的例子一直使用的dispatch_async,既然有async(非同步 - asynchronous),当然也就有sync(同步 - synchronous)落!
- dispatch_async是将指定的Block“非同步”地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待!
- dispatch_sync将指定的Block"同步"地追加到指定的Dispatch Queue中,在追加Block结束之前,dispatch_sync函数会一直等待!
另外类比的还有dispatch_barrier_async和dispatch_barrier_sync两个函数!!
sync这类函数容易造成程序死锁,所以建议少用!!!
例如:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue,^{
NSLog(@"hello?");
});
dispatch_async(queue,^{
dispatch_sync(queue,^{
NSLog(@"helloc?");
dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
使用方法(遍历NSArray):
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_apply([array count],queue,^(size_t index){
NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
});
NSLog(@"done");
输出结果:
4
1
0
3
5
2
6
8
9
7
done
另外由于dispatch_apply函数与dispatch_sync一样会等待处理执行结束,因此推荐在dispatch_async函数中异步执行dispatch_apply函数。
使用方法:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_async(queue,^{
dispatch_apply([array count],queue,^(size_t index){
NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(),^{
/*
更新UI
*/
NSLog(@"done");
});
});
dispatch_suspend、dispatch_resume
当追加大量处理到Dispatch Queue(一定要注意是Queue)时,在追加处理的过程中,有时希望不执行已追加的处理。在这种情况情况下,只要挂起Dispatch Queue即可,当可以执行时再恢复。
挂起指定的Dispatch Queue
dispatch_suspend(queue)
恢复指定的Dispatch Queue
dispatch_resume(queue)
这些函数对已经执行的处理没有影响,当挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行,当恢复后,这些处理将继续执行。
Dispatch Semaphone
Dispatch Semaphone是持有计数的信号,该计数是多线程编写中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。而在Dispatch Semaphone中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。
使用方法:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_semaphone_t semaphone = dispatch_semaphone_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; i++) {
dispatch_async(queue,^{
dispatch_semaphone_wait(semaphone,DISPATCH_TIME_FOREVER); // 计数值-1
[array addObject:@(i)];
dispatch_semaphone_signal(semaphone); // 计数值+1
});
}
在没有Serial Dispatch Queue和dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,Dispatch Semaphone便可发挥威力。
dispatch_once
dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。主要是在多核CPU中,可能会发生多次执行某个操作。使用dispatch_once就可保证多核CPU、多线程中都仅仅执行一次!
多使用于Objective-C中的单例模式
Dispatch I/O
在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。现今的输入/输出硬件已经可以做到一次使用多个线程更快的并列读取了,能实现这一功能的就是Dispatch I/O和Dispatch Data。
通过Dispatch I/O读写文件时,使用Global Dispatch Queue将1个文件按某个大小read/write。
dispatch_async(queue,^{
// 读取 0 ~ 8191字节
});
dispatch_async(queue,^{
// 读取 8192 ~ 16383字节
});
dispatch_async(queue,^{
// 读取 16384 ~ 24575字节
});
dispatch_async(queue,^{
// 读取 24576 ~ 32767字节
});
dispatch_async(queue,^{
// 读取 32768 ~ 40959字节
});
可像上面一下,将文件分割为一块一块读取。读取的数据可以通过Dispatch Data进行结合。例如:
dispatch_queue_t pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair(1);
dispatch_io_set_water(pipe_channel,SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0) {
size_t len = dispatch_data_get_size(pipedata);
if (len ==0 ) {
const char *bytes = NULL;
char *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void**)&bytes, &len);
....
}
}
});
如果想提高文件读取速度,可以尝试使用Dispatch I/O
Dispatch Source
监控文件夹的变化
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *directoryURL = [NSURL fileURLWithPath:documentsDirectory isDirectory:YES];
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
char buffer[80];
strerror_r(errno, buffer, sizeof(buffer));
NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
unsigned long const data = dispatch_source_get_data(source);
if (data & DISPATCH_VNODE_WRITE) {
NSLog(@"The directory changed.");
}
if (data & DISPATCH_VNODE_DELETE) {
NSLog(@"The directory has been deleted.");
}
});
dispatch_source_set_cancel_handler(source, ^(){
close(fd);
});
dispatch_resume(source);