iOS开发_GCD、dispatch函数介绍

iOS多线程的方法有3种:

NSThread

NSOperation

GCD(Grand Central Dispatch)

 其中,由苹果所倡导的为多核的并行运算提出的解决方案:GCD能够访问线程池,并且可在应用的整个生命的周期里面使用,一般来说,GCD会尽量维护一些适合机器体系结构的线程,在有工作需求的时候,自动利用更多的处理器核心,以此来充分使用更强大的机器系统性能。在以前,iOS设备为单核处理器的,线程池的用处并不大,但是现在的移动设备,包括iOS设备,愈发地朝多核的方向迈进,因此GCD中的线程池,能够在此类设备中,能够使得强大的硬件系统性能上得到更加完善的利用。

GCD,无疑是最便捷的,基于C语言的所设计的。在使用GCD的过程中,最方便的,莫过于不需要编写基础线程代码,其生命周期也不需要手动管理;创建需要的任务,然后添加到已创建好的queue队列,GCD便会负责创建线程和调度任务,由系统直接提供线程管理。

这样一种多线程的方式,我们也会在实际项目中经常看到:app中,由于数据的执行与交换所消耗的时间长,导致需要反馈给用户UI界面往往出现延迟的现象。这样我们可以通过多线程的方法,让需要调用的方法在后台执行、在主线程上进行UI界面的切换,这样不仅是用户体验更加友好美观,也使得程序设计井然有序。

本文主要粗略介绍GCD的一般使用,以及GCD中dispatch_前缀方法调用的作用和使用范围。

UI界面如下图,通过创建4个按钮事件,分析4种不同的函数所执行的程序块运行方式:

【本次开发环境: Xcode:7.2     iOS Simulator:iphone6   By:啊左】


一、GCD的使用

GCD对于开发者来说,最简单的,就是通过调用dispatch把一连串的异步任务添加到队列中,进行异步执行操作。

代码调用如下:

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

async表示异步运行;

queue为我们提前创建的队列;

block也就是“块”,让我们执行事件的模块;

async(异步)与sync(同步):

当然,我们也可以使用同步任务,使用dispatch_sync函数添加到相应的队列中,而这个函数会阻塞当前调用线程,直到相应任务完成执行。

但是,也正因为这样的同步特性,在实际项目中,当有同步任务添加到正在执行同步任务的队列时,串行的队列会出现死锁。而且由于同步任务会阻塞主线程的运行,可能会导致某个事件无法响应。

队列(queue):

需要注意的是,调用dispatch_async不会让块运行,而是把块添加到队列末尾。队列不是线程,它的作用是组织块。(如果读者学过数据结构的知识,就会知道队列的基本特征如饭堂排队队,先到的排前面,先打到饭,也就是“先进先出”原理)

在GCD中,可以给开发者调用的常见公共队列有以下两种:

dispatch_get_global_queue:用于获取应用全局共享的并发队列(提供多个线程来执行任务,所以可以按序启动多个任务并发执行。可用于后台执行任务)

dispatch_get_main_queue:   用于获取应用主线程关联的串行调度队列(只提供一个线程执行任务。运行的main主线程,一般用于UI的搭建)

(还有另外一种,dispatch_get_current_queue,用于获取当前正在执行任务的队列,主要用于调试,但是在iOS 6.0之后苹果已经废弃,原因是容易造成死锁。详情可以查看官方注释。)

这两种公共队列的调用便可以解决我们刚刚关于后台执行任务、主线程用于更新UI界面的问题,

结构如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

//把逻辑计算等需要消耗长时间的任务,放在此处的全局共享的并发队列执行;dispatch_async(dispatch_get_main_queue(), ^{

//回到主线程更新UI界面;

});

});

例如在有一些项目中,会涉及到异步下载图片,这个时候就可以使用这样一种结构来进行任务的分配:

//异步下载图片dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{//先把下载数据的任务放在全局共享并发队列中执行

NSURL *url = [NSURL URLWithString:@"图片的URL"];

NSData* data =[[NSData alloc]initWithContentsOfURL:url];

UIImage*image =[UIImage imageWithData:data];if(data !=nil)

{//完成后,回到主线程显示图片

dispatch_async(dispatch_get_main_queue(), ^{

self.imageView.image=image;

});

}

});

二、串行队列 and  并行队列

1.串行(Serial)的执行:指同一时间每次只能执行一个任务。 线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。

可以添加多个任务到队列中,执行次序FIFO,但是当程序需要执行大量的任务时,虽然系统允许,但是鉴于程序的资源分配,应该交给全局并发队列来完成才能更好地发挥系统性能。

创建串行队列的方式如下:

dispatch_queue_t serialQueue = dispatch_queue_create("zuoA", NULL);//第一个参数是队列的名称,通常使用公司的反域名;第二个参数是队列相关属性,一般用NULL.

关于什么是FIFO次序,我们用代码解释一下

- (IBAction)SerialQueue:(UIButton *)sender {

dispatch_queue_t serialQueue= dispatch_queue_create("zuoA", NULL);

dispatch_async(serialQueue,^{

sleep(3);

NSLog(@"A任务");

});

dispatch_async(serialQueue,^{

sleep(2);

NSLog(@"B任务");

});

dispatch_async(serialQueue,^{

sleep(1);

NSLog(@"C任务");

});

}

console控制台显示如下:

2016-03-15 15:04:11.909 dispatch_queue的多任务GCD使用[92316:2538875] A任务

2016-03-15 15:04:13.910 dispatch_queue的多任务GCD使用[92316:2538875] B任务

2016-03-15 15:04:14.910 dispatch_queue的多任务GCD使用[92316:2538875] C任务

可以看得到,即使需要等待几秒,后面所添加的任务也必须等待前面的任务完成后才能执行,类似我们前面所讲"饭堂"排队的例子,队列完全按照"先进先出"的顺序,也即是所执行的顺序取决于:开发者将工作任务添加进队列的顺序。

2.并行(concurrent)的执行:可同一时间可以同时执行多个任务。

负荷:并发执行任务与系统有关,能够同时执行任务的数量是由系统根据应用和此时的系统状态等动态变化决定的。

顺序:由于并行队列也是队列(吐槽这是废话T^T),因此每个任务的启动时间也是按照FIFO次序,也就是加入queue的顺序,但是结束的顺序则依          赖各自的任务所需要消耗的时间。

与串行的不同:虽然启动时间一致,但是这是“并发执行”,因此不需要等到上一个任务完成后才进行下一个任务。所以每个块中的各部分的先后执行的顺序需要视情况而定。

上代码,找不同。。。

- (IBAction)concurrentQueue:(UIButton *)sender {

dispatch_queue_t concurrentQueue

=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_async(concurrentQueue,^{

dispatch_async(concurrentQueue,^{

sleep(3);

NSLog(@"A任务");

});

dispatch_async(concurrentQueue,^{

sleep(2);

NSLog(@"B任务");

});

dispatch_async(concurrentQueue,^{

sleep(1);

NSLog(@"C任务");

});

});

}

console控制台显示如下:

2016-03-15 15:02:06.911 dispatch_queue的多任务GCD使用[92294:2537296] C任务

2016-03-15 15:02:07.907dispatch_queue的多任务GCD使用[92294:2537147] B任务

2016-03-15 15:02:08.908dispatch_queue的多任务GCD使用[92294:2537177] A任务

通过控制台左边的时间记录,可以看到,与串行队列不同的是,并行队列中这3个任务的并行启用,与串行不同的是,不需要等到A任务调用完,就已经在调用B、C,显著地提高了线程的执行速度,凸显了并行队列所执行的异步操作的并行特性;

另外,从这段代码中,不同的是串行队列需要创建一个新的队列,而并行队列中,只需要调用iOS系统中为我们提供的全局共享dispatch_get_global_queue就可以了:

dispatch_queue_t concurrentQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

第一个参数为iOS系统为全局共享队列提供4种调度的方式,主要区别即是优先级的不同而已:

DISPATCH_QUEUE_PRIORITY_HIGH

DISPATCH_QUEUE_PRIORITY_DEFAULT

DISPATCH_QUEUE_PRIORITY_LOW

DISPATCH_QUEUE_PRIORITY_BACKGROUND

我们采用默认的DISPATCH_QUEUE_PRIORITY_DEFAULT方式,而右边的第二个参数是苹果预留的,暂时没有其他的含义,所以,一般默认为:0。

并发的好处就是不需要像串行一样按照顺序执行,并发执行可以显著地提高速度。

三、dispatch_group_async的使用

有时候,我们会遇到这样的情况,UI界面部分的显示,需要在完成几个任务再进行主任务,例如3张图片下载完毕,才通知UI界面已经完成任务。

我们可以通过分派组(dispatch group)进行并发程序块分配的运用,将异步分派(dispatch_async)的所有程序块设置为松散,或者分配给多个线程来执行,监听到这组任务全部完成后,使用dispatch_group_notify()通知并调用notify中的块,例如UI界面的程序块。

代码:

- (IBAction)groupQueue:(UIButton *)sender {

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,^{

sleep(3);

NSLog(@"A任务");

});

dispatch_group_async(group, queue,^{

sleep(2);

NSLog(@"B任务");

});

//group组中的任务完后,通知并调用notify中的块

dispatch_group_notify(group, queue, ^{

NSLog(@"主任务");

});

}

console控制台显示如下:

2016-03-16 11:18:41.306 dispatch_queue的多任务GCD使用[94865:2718342] B任务

2016-03-16 11:18:42.302 dispatch_queue的多任务GCD使用[94865:2718341] A任务

2016-03-16 11:18:42.303 dispatch_queue的多任务GCD使用[94865:2718341] 主任务

结果验证了前面说的,直到分派组任务都完后,notify添加的任务块才会执行。

眼尖的读者可能也发现,整个任务组完成的时间比2个任务分别运行的时间还要短!这得益于我们同时进行了两种计算~

当然在真实的开发运用中,这种明显运行时间缩短的效果,取决于所需要执行的工作量和可用的资源,以及多个CPU核心的可用性,因此在多核技术日益完善的大环境下,这样一种多线程技术将得到更有效的利用。

四、dispatch_barrier_async的使用

dispatch_barrier(分派屏障)是当前面的任务执行完后,才执行barrier块的任务,而且后面的任务也得等到barrier块的执行完毕后才能开始执行。

很好地突显了“障碍物”这样的特性,那么代码上应该怎么写呢?

按照并发的性质,我们在barrierQueue方法中敲入以下代码:

- (IBAction)barrierQueue:(UIButton *)sender {

dispatch_queue_t queue

=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_async(queue,^{

sleep(2);

NSLog(@"A任务");

});

dispatch_async(queue,^{

sleep(1);

NSLog(@"B任务");

});

dispatch_barrier_async(queue,^{

NSLog(@"barrier任务");

});

dispatch_async(queue,^{

sleep(1);

NSLog(@"C任务");

});

}

console控制台显示如下:

2016-03-16 13:18:47.525 dispatch_queue的多任务GCD使用[95191:2752854] barrier任务

2016-03-16 13:18:48.529 dispatch_queue的多任务GCD使用[95191:2752839] B任务

2016-03-16 13:18:48.529 dispatch_queue的多任务GCD使用[95191:2752844] C任务

2016-03-16 13:18:49.528 dispatch_queue的多任务GCD使用[95191:2752840] A任务

任务的执行顺序依然是跟并行队列的方法一样,barrier没有发挥它的“障碍物”的界限作用。这是因为barrier这一块是依赖队列queue的模型来执行的,当队列为全局共享时,barrier就无法发挥其作用。我们需要新创建一个队列,

dispatch_queue_t queue = 

dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL);

完整的程序如下:

- (IBAction)barrierQueue:(UIButton *)sender {

dispatch_queue_t queue= 

dispatch_queue_create("zuoA", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue,^{

sleep(2);

NSLog(@"A任务");

});

dispatch_async(queue,^{

sleep(1);

NSLog(@"B任务");

});

dispatch_barrier_async(queue,^{

NSLog(@"barrier任务");

});

dispatch_async(queue,^{

sleep(1);

NSLog(@"C任务");

});

}

console控制台显示如下:

2016-03-16 13:30:14.251 dispatch_queue的多任务GCD使用[95263:2759658] A任务

2016-03-16 13:30:15.255 dispatch_queue的多任务GCD使用[95263:2759658] B任务

2016-03-16 13:30:15.255 dispatch_queue的多任务GCD使用[95263:2759658] barrier任务

2016-03-16 13:30:16.256 dispatch_queue的多任务GCD使用[95263:2759658] C任务

这就是我们想要得到的效果:确实只有在前面A、B任务完成后,barrier任务才能执行,最后才能执行C任务。

那么,dispatch_queue_create为什么要用DISPATCH_QUEUE_SERIAL,可以用其他么?答案是肯定的。把参数换成DISPATCH_QUEUE_CONCURRENT,

可以得到以下输出:

2016-03-16 13:34:23.855 dispatch_queue的多任务GCD使用[95294:2762604] B任务

2016-03-16 13:34:24.853 dispatch_queue的多任务GCD使用[95294:2762603] A任务

2016-03-16 13:34:24.853 dispatch_queue的多任务GCD使用[95294:2762603] barrier任务

2016-03-16 13:34:25.856 dispatch_queue的多任务GCD使用[95294:2762603] C任务

也就是说,A、B、C任务完全是按照队列的顺序执行,只是由于barrier块的“屏障”作用,把A、B任务放在前面,而使得后来加入的C任务只有等到barrier块执行完毕才能运行;

五、dispatch_suspend(暂停)和 dispatch_resume(继续)

暂停:当需要暂停某个队列queue时, 调用dispatch_suspend(queue),此时阻止了queue执行块对象,且queue的引用计数增加;

继续:继续queue时,调用dispatch_resume(queue),此时queue启动执行块的操作,queue的引用计数减少;

需要注意的是,suspend与resume是异步的,只在block块之间调用,而且必须是成对存在的。

还有一些其他的dispatch函数,例如

dispatch_once:可以使特定的块在整个应用程序生命周期中只被执行一次~(在单例模式中使用到.)

dispatch_apply:执行某个代码片段n次(开发者可以自己设定)。

dispatch_after:当我们需要等待几秒后进行某个操作,可以使用这个函数;

注意事项:

1.在上面的例子中,我们没有使用过手动内管其内存,因为系统会自动管理。

如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8

就需要自己管理GCD对象,使用(dispatch_retain,dispatch_release),ARC并不会去管理它们

对于最低sdk版本>=ios6.0来说,GCD对象已经纳入了ARC的管理范围,我们就不需要再手工调用 dispatch_release和dispatch_retain函数来增加和减少引用计数以管理内存了。

2.之所以一直在说dispatch函数,而不是方法,方法归类、对象所有,是因为dispatch是C语言下的函数,函数与方法是不同的,函数归文件所有。

函数与方法有什么不同?看过一篇博文:OC中方法与函数的区别这个有兴趣的童鞋可以了解一下。

本文旨在介绍dispatch_等函数的运用,文字篇幅有些大,敬请见谅(╯▽╰)



(转载请标明原文出处,谢谢支持 ~ ^-^ ~)

 by:啊左~

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

推荐阅读更多精彩内容

  • iOS中GCD的使用小结 作者dullgrass 2015.11.20 09:41*字数 4996阅读 20199...
    DanDanC阅读 810评论 0 0
  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 848评论 0 2
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,015评论 0 4
  • 目录(GCD): 关键词 混淆点 场景应用 总结 1. 关键词 线程概念: 独立执行的代码段,一个线程同时间只能执...
    Ryan___阅读 1,259评论 0 3
  • 工作日志 哥哥已经有2天没写日志了!不是不想写,只是不知道怎么写! why? 因为周一周二的产出物为**读书笔记*...
    钱佳辰_Hangzhou阅读 187评论 0 0