iOS图形图像及核心动画实战三GCD基础知识

本教程是一个合集,涉及到的目录结构:
基础知识总结
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

  1. 通过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);
  2. 获取系统标准提供
    • 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)
对比表
名称 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)落!

  1. dispatch_async是将指定的Block“非同步”地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待
  2. 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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 850评论 0 2
  • 我们知道在iOS开发中,一共有四种多线程技术:pthread,NSThread,GCD,NSOperation: ...
    请叫我周小帅阅读 1,484评论 0 1
  • Grand Central Dispatch(GCD)概要 我的博客链接 什么是GCD? 苹果官方这么描述的:Gr...
    换个名字再说阅读 1,276评论 4 7
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,226评论 21 42
  • 一、GCD的API 1. Dispatch queue 在执行处理时存在两种Dispatch queue: 等待现...
    doudo阅读 493评论 0 0