iOS开发——GCD的使用

Grand Central Dispatch(GCD)概要

我的博客链接

什么是GCD?

苹果官方这么描述的:Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级别中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

看一个GCD的例子:

dispatch_async(queue, ^{
  /*
   * 长时间处理,比如数据库访问等等操作
   */
   //长时间处理结束,主线程使用该处理结果
   dispatch_async(dispatch_get_main_queue(), ^{
     /*
      * 只在主线程可以执行的处理
      * 比如用户界面更新
      */
   });
});

GCD的API

Dispatch Queue

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue。如下:

dispatch_async(queue, ^{
  //想要执行的任务
});

该源代码使用Block语法定义想执行的任务,通过dispatch_async函数“追加”赋值在变量queue的Dispatch Queue中。这样就可以使指定的Block再另一线程中执行。

Dispatch Queue是执行处理的等待队列。🐶程序员可以通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序(先进先出FIFO)执行处理。

1.png

在执行处理时有两种Dispatch Queue一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue

Dispatch �Queue 的种类 说明
Serial Dispatch Queue 等待现在执行中处理结束
Concurrent Dispatch Queue 不等待现在执行中处理结束

看一下下面这段代码:

dispatch_async(queue, blk0);
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_async(queue, blk3);
dispatch_async(queue, blk4);
dispatch_async(queue, blk5);
dispatch_async(queue, blk6);
dispatch_async(queue, blk7);

当变量queueSerial Dispatch Queue时,需要等待现在执行中的处理结束,所以首先执行blk0,blk0执行结束之后,接着执行blk1,blk1结束后再执行blk3,如此重复。同时执行的处理数只能有1个。所以执行改代码一定按照blk0、blk1、blk2、blk3、blk4、blk5、blk6、blk7的顺序执行。

当变量queueConcurrent Dispatch Queue时,因为不用等待现在执行中的处理结束,所以首先执行blk0,不管blk0是否结束,都开始执行后面的blk1,不管blk1的执行是否结束,都开始执行后面的blk2,如此重复循环。虽然这样不用等待处理结束,可以并行执行多个处理,但是并行的处理数量取决于当前系统的状态。即iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。并行执行就是使用多个线程执行多个处理。前面的源代码的一种执行情况:

线程0 线程1 线程2 线程3
blk0 blk1 blk2 blk3
blk4 blk6 blk5
blk7

首先blk0在线程0中开始执行,接着blk1在线程1中,blk2在线程2中、blk3在线程3中开始执行。线程0中blk0执行结束后开始执行blk4,由于线程1中blk1还没有结束,此时线程2中的blk2执行结束,线程2开始执行blk5,线程1中blk1执行结束,线程1开始执行blk6,如此重复。像这样在Concurrent Dispatch Queue中执行处理时,执行顺序会根据处理内容和系统状态发生改变。

看一下Serial Dispatch QueueConcurrent Dispatch Queue和线程的关系:

2.png

dispatch_queue_create

这是获得Dispatch Queue的第一种方法,通过dispatch_queue_create函数可生成Dispatch Queue。看一个示例:

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("cn.flametao.www.gcd.mySerialDispatchQueue", NULL);

dispatch_queue_create函数的第一个参数指定Dispatch Queue的名称,推荐使用应用程序ID这种逆序全程域名(FQDN,fully qualified domain name)。该名称会出现在调试过程中或程序崩溃时所产生的CrashLog中。第二个参数指定为NULL时,生成的是Serial Dispatch Queue,指定为DISPATCH_QUEUE_CONCURRENT时,生成的是Concurrent Dispatch Queue。像下面源代码一样:

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("cn.flame.www.gcd.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_queue_create返回值为表示Dispatch Queuedispatch_queue_t类型。

现在来说一说关于生成Serial Dispatch Queue个数的注意事项,如前所述,Concurrent Dispatch Queue并行执行多个追加处理,Serial Dispatch Queue同时只能处理1个追加处理。虽然这两者都受到系统资源限制,但是dispatch_queue_create函数可生成任意多个Dispatch Queue

当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue在一起将并行执行,虽然每一个Serial Dispatch Queue中同时只能执行一个追加处理。图示:

3.png

过多的使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅降低系统的响应性能。

使用Serial Dispatch Queue避免多线程编程问题时的数据竞争问题:

4.png

Dispatch Queue没有像Block那样具有作为Objective-C对象来处理的技术,所以没办法通过ARC来自动管理内存,只能由程序员负责释放。通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release函数释放。

dispatch_release(mySerialDispatchQueue);

看一下这样的代码:

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("cn.flametao.www.gcd.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on myConcurrentDispatchQueue");});
dispatch_release(myConcurrentDispatchQueue);

该源代码中,在dispatch_async函数中追加Block到Concurrent Dispatch Queue中,并立即通过dispatch_release函数进行释放是否可以呢?是没有问题的!Block通过dispatch_async函数持有Dispatch Queue。无论是Serial Dispatch Queue还是Concurrent Dispatch Queue都一样。当Block执行结束,才通过dispatch_release函数释放该Block持有的Dispatch Queue

Main Dispatch Queue/Global Dispatch Queue

第二种方法是获得系统标准提供的Dispatch Queue

系统为我们提供了几个Dispatch Queue,就是Main Dispatch QueueGlobal Dispatch Queue

Main Dispatch Queue顾名思义,就是在主线程中执行的Dispatch Queue。因为主线程只有一个,所以Main Dispatch QueueSerial Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。

Global Dispatch Queue是所有应用程序都能够使用的的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue,只需要获取Global Dispatch Queue使用即可。Global Dispatch Queue有4个执行优先级:

  • 高优先级(High Priority)
  • 默认优先级(Default Priority)
  • 低优先级(Low Priority)
  • 后台优先级(Background Priority)

系统提供的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 Queue获取方法:

//Main Dispatch Queue
dispatch_queue_t mainDispatchQeuue = dispatch_get_main_queue();

//Global Dispatch Queue(High Priority)
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

//Global Dispatch Queue(Default Priority)
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//Global Dispatch Queue(Low Priority)
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

//Global Dispatch Queue(Default Priority)
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

Main Dispatch QueueGlobal Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会起任何作用。

dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。可以通过diapatch_set_target_queue函数改变优先级。比如,在后台执行Serial Dispatch Queue

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("cn.flametao.www.gcd.mySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

第一个参数为需要变更优先级的Dispatch Queue,第二个参数为参照优先级的Dispatch Queue第一个参数不可以是Main Dispatch Queue或者是Global Dispatch Queue

不仅仅可以改变Dispatch Queue执行优先级,还可以作为Dispatch Queue的执行层。如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定某一个Serial Dispatch Queue,原本应该并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能执行一个处理。因此可以利用这个防止处理并行执行。

5.png

dispatch_after

dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
  ...
});

第一个参数是指定时间用的dispatch_time_t类型的值。该值使用dispatch_timedispatch_walltime制成。

第二个参数是指定要追加处理的Dispatch Queue

第三个参数是指定记述要执行处理的Block。

可以使用NSEC_PER_MSEC计算毫秒,"ull"是C语言的数值字面量,是显示表明类型时使用的字符串(表示“unsigned long long”)。

dispatch_walltime函数由POSIX种使用的struct timespec类型的时间得到dispatch_time_t类型的值。dispatch_time函数通常计算相对时间,dispatch_walltime函数用于计算绝对时间。struct timespec类型的可以通过NSDate作成:

dispatch_time_t getDispatchTimeByDate(NSDate *date) {
  NSTimeInterval interval;
  double second, subsecond;
  struct timespec time;
  dispatch_time_t milestone;
  interval = [date timeIntervalSince1970];
  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中的多个处理全部结束后想执行结束处理。当只有一个Serial Dispatch Queue时,追加在最后即可。但是使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,可以使用Dispatch Group

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, ^{//blk0});
dispatch_group_async(group, queue, ^{//blk1});
dispatch_group_async(group, queue, ^{//blk2});

dispatch_group_notify(goup, dispatch_get_main_queue(), ^{//blk3});

blk3一定是最后执行的,前面顺序由于多线程并行执行所以不确定。dispatch_group_async函数与dispatch_async函数相同,都追加Block到指定的Dispatch Queue中。dispatch_group_async函数指定生成的Dispatch Group为第一个参数,指定的Block属于指定的Dispatch Group在追加到Dispatch Group中的处理全部执行结束时,dispatch_group_notify函数会将执行的Block追加到Dispatch Queue

dispatch_group_wait函数仅等待全部处理执行结束。返回值不为0就意味着虽然经过了指定的时间,但属于Dispatch Group的某个处理还在执行中。如果返回0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER必定返回0。指定为DISPATCH_TIME_NOW则不用任何等待即可判断属于Dispatch Group的处理是否执行结束。不过,推荐使用dispatch_group_notify函数追加结束处理,可以简化代码。

和之前一样,create出来的需要手动释放:

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, ^{//blk0});
dispatch_group_async(group, queue, ^{//blk1});
dispatch_group_async(group, queue, ^{//blk2});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

dispatch_barrier_async

学过《操作系统》课程的都应该知道读写者问题

dispatch_queue_t queue = dispatch_queue_create("cn.flametao.www.gcd.queue", DISPATCH_QUEUE_CONCURRENT);

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);

blk_writing之前的追加到Concurrent Dispatch Queue的处理全部执行完,才执行blk_writing,执行完blk_writing,在接着执行后面追加的处理。可能的执行情况:

6.png

使用Concurrent Dispatch Queuedispatch_barrier_async函数可实现高效率的数据库访问和文件访问。

dispatch_sync

dispatch_async函数的async意味着“非同步”,就是将指定的Block“非同步”地追加到指定的Dispatch Queue中。dispatch_async不做任何等待。

dispatch_sync函数的sync意味着“同步”,也就是将制定的Block“同步”追加到指定的Dispatch Queue中。在追加的Block结束之前,dispatch_sync函数会一直等待。

dispatch_group_wait函数说明所示,“等待”意味着当前线程停止。

学过汇编的应该知道,当主程序Call一个函数还没有返回的时候,CS、IP寄存器就没恢复到Call之前的状态,也就没办法继续主程序。dispatch_sync很容易造成死锁。

例如:在主线程中执行以下源代码就会死锁:

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{...});

该源代码想在主线程中执行指定的Block,并等待Block运行结束,而追加的Block放在了Main Dispatch Queue的最后(按照FIFO),主线程此时在等待dispatch_sync返回,而dispatch_sync在等待主线程执行完Block,从而死锁。图解:

7.png

只要dispatch_sync的追加处理的queue为当前queue(并行的除外),会产生死锁。

dispatch_apply

dispatch_apply(size_t iterations, dispatch_queue_t  _Nonnull queue, ^(size_t)block)

dispatch_apply函数是dispatch_syncDispatch Group的关联API。该函数按指定的次数将制定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理,是一个带有参数的Block,按第一个参数重复追加Block并区分各个
Block而使用,例如对NSArray类对象的所有元素执行处理时不需要一个一个编写for循环部分。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index){
    NSLog(@"%zu", index);
});
NSLog(@"done");

一种执行结果:

0
1
2
3
4
5
6
9
7
8
done

因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定。但是输出结果“done”必定在最后位置。因为dispatch_apply函数会等待全部处理执行结束。

由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束。因此推荐在dispatch_async函数中非同步执行dispatch_apply函数。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//在Global Dispatch Queue中非同步执行
dispatch_async(queue, ^{
  //等待dispatch_apply函数中的处理全部执行结束
  dispatch_apply([array count], queue, ^(size_t index){
    //并列处理包含在NSArray对象的全部对象
    NSLog(@"%zu: %@", index, [array objectAtIndex]);
  });
  //dispatch_apply中的处理全部执行结束
  //在Mian Dispatch Queue中非同步执行
  dispatch_async(dispatch_get_main_queue(), ^{
    //...
  });
});

dispatch_suspend/dispatch_resume

dispatch_suspend函数挂起指定的Dispatch Queue

dispatch_suspend(queue);

dispatch_resume函数恢复指定的Dispatch Queue

dispatch_resume(queue);

这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中尚未执行的处理在此之后停止执行直到恢复

Dispatch Semaphore

和操作系统中的信号量机制类似,解决多线程中的数据不一致问题。之前说的Serial Dispatch Queuedispatch_barrier_async也可简单解决这个问题,但有必要进行更细的排他控制。

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。计数为0是等待,计数大于0时,减去1而不等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

参数表示计数的初始值(效果等同同时访问的线程数量)。通前面一样“create”出来的必须通过dispatch_release释放。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch Semaphore的技术支持大于等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。第二个参数通dispatch_group_wait函数相同,有dispatch_time_t类型值指定等待时间。

dispathc_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
  /*
   * 由于Dispatch Semaphore的计数值达到大于或等于1
   * 或者在待机中的指定时间内计数值达到大于或等于1
   * 所以Dispatch Semaphore的计数值减去1
   * 可安全的执行需要进行排他控制的处理
   */
} else {
  /*
   * 由于 Dispatch Semaphore 的计数值为0
   * 因此在达到指定时间为止待机
   */
}

处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数加1。

dispatch_once

dispatch_once函数保证在应用程序执行中只执行一次指定处理的API。举个初始化的例子

static int once = NO;
if (once == NO) {
  // 初始化
  once == YES;
}
// 如果使用dispatch_once,则为:
static dispatch_once_t once;
dispatch_once(&once, ^{
  // 初始化
});

两者看起来没多大区别,但是通过dispatch_once函数使在多线程环境中也可保证百分百安全。
注:参考书籍《Objective-C高级编程》

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

推荐阅读更多精彩内容

  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 848评论 0 2
  • 多线程概念 线程线程指的是:1个CPU执行的CPU命令列为一条无分叉路径 多线程这种无分叉路径不止一条,存在多条即...
    我系哆啦阅读 571评论 0 5
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,226评论 21 42
  • 一. GCD和多线程的介绍 GCD GCD是异步执行任务的技术之一。开发者只需要定义想执行的任务并追加到适当的Di...
    WellsCai阅读 277评论 0 1
  • 我们知道在iOS开发中,一共有四种多线程技术:pthread,NSThread,GCD,NSOperation: ...
    请叫我周小帅阅读 1,482评论 0 1