GCD

概念解释

1. 执行任务的函数:在GCD中,任务是通过 block来封装的,并且任务的block没有参数也没有返回值。

同步:你必须把我的代码执行完你再走,一定要执行完同步里的代码再执行下面的代码

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

异步:你先走执行我下面的代码,我找人、找线程去执行我里面的代码

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2. GCD使用步骤:

第一步: 创建/获取 队列
第二步: 创建任务,确定要做的事情
第三步: 将任务添加到队列中
(1)GCD会自动将队列中的任务取出,放到对应的线程中执行
(2)任务的取出遵循队列的FIFO原则: 先进先出,后进后出

3. 队列

包括: 串行队列、并发队列、主队列、全局队列

1. 串行队列(Serial Dispatch Queue)

串行队列的特点:
以先进先出的方式,按顺序调度队列中的任务去执行,一次只能调度一个任务。
无论队列中所指定的执行任务的函数是同步还是异步,都必须等待前一个任务执行完毕,才可以调度后面的任务。

串行队列的创建:

(1) dispatch_queue_tqueue = dispatch_queue_create("itheima", DISPATCH_QUEUE_SERIAL);
(2) dispatch_queue_tqueue = dispatch_queue_create("itheima", NULL);

串行队列,同步执行:
开不开线程? 不开线程。
顺序执行还是乱序执行? 顺序执行。

串行队列,异步执行:
开不开线程? 只会开一条线程。
顺序执行还是乱序执行? 顺序执行。

2. 并发队列(Concurrent Dispatch Queue)
  • 并发队列的特点:以先进先出的方式,并发(同时)调度队列中的任务去执行。
  • 如果当前调度的任务是同步执行的,会等待当前任务执行完毕后,再调度后续的任务。
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,就不会等待当前任务,直接调度任务到新线程去执行。

并发队列的创建:

dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);

并发队列,同步执行:
开不开线程? 不开线程。
顺序执行还是乱序执行? 顺序执行。
并发同步 和 串行同步的执行结果一模一样。

并发队列,异步执行:
开不开线程? 开多条新线程。
顺序执行还是乱序执行? 乱序执行。

3. 主队列:
  • 遇到主队列,不管同步异步都要先执行完主线程里的代码再执行主队列里的代码
  • dispatch_sync方法不能在主队列中调用,因为这会无限期的阻止线程并会导致你的应用死锁。所有通过GCD提交到主队列的任务必须是异步的。
  • 只有当主线程空闲时, 主队列才会调度任务到主线程执行
  • 主队列是系统提供的,无需自己创建,可以直接通过dispatch_get_main_queue()函数来获取。
  • 主队列的特点:先执行完主线程上的代码,才会执行主队列中的任务
    1、添加到主队列的任务只能由主线程来执行。
    2、以先进先出的方式,只有当主线程的代码执行完毕后,主队列才会调度任务到主线程执行。
    如下列代码执行打印123后再打印hello
- (void)demo1 {    
for (int i = 0; i < 10; i++) {       
NSLog(@"111");        
dispatch_async(dispatch_get_main_queue(), ^{            
NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });        
NSLog(@"222");        
NSLog(@"333");    
}
}
  • 主队列,同步执行(死锁)主线程和主队列同步任务相互等待,造成死锁。
    执行到同步主队列后,同步时主线程想让里面代码顺序执行下去(直接进去执行里面代码),而主队列要求执行完毕主线程代码再执行里面代码,导致谁都无法继续执行,导致死锁。
- (void)viewDidLoad {    
[super viewDidLoad];    
NSLog(@"begin");    
for (int i = 0; i < 10; i++) {        //死锁,一看同步就不分子线程了,一看主队列,就等着主线程执行完来执行里面代码       
dispatch_sync(dispatch_get_main_queue(), ^{            
NSLog(@"hello %d  %@",i,[NSThread currentThread]);        
});    }    
NSLog(@"end");
}
  • 解决死锁的问题,全局队列异步执行,里面嵌套主队列同步执行
    当我们将主队列同步执行任务放到子线程去执行,就不会出现死锁。
    由于将主队列同步放到了子线程中执行,主队列同步任务无法阻塞主线程执行代码,因此主线程可以将主线程上的代码执行完毕。当主线程执行完毕之后,就会执行主队列里面的任务。

先执行begin和end,再顺序执行全局队列异步执行(第二个线程里执行)

- (void)demo3 {
    NSLog(@"begin");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 10; i++) {
            dispatch_sync(dispatch_get_main_queue(), ^{                
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);            });        }
    });
    NSLog(@"end");
}
  • 主队列,异步执行
    执行到异步主队列后,里面代码异步执行后释放(不创建新线程),主线程执行完毕后回来调用主队列中代码

  • 开不开线程? 主队列,就算是异步执行,也不会开线程。

  • 顺序执行还是乱序执行? 顺序执行。

  • 先把主线程上的代码执行完毕,才会执行添加到主队列里面的任务。

  • 主队列和串行队列的区别

  • 串行队列:必须等待一个任务执行完毕,才会调度下一个任务,顺序执行代码。

  • 主队列:如果主线程上有代码执行,主队列就不调度任务,跳过主队列代码执行主线程代码,完毕后再执行主队列内代码。

4. 全局队列:
  • 全局队列是系统提供的,无需自己创建,可以直接通过dispatch_get_global_queue(long identifier, unsigned long flags);函数来获取。

  • 全局队列的工作特性跟并发队列一致。 实际上,全局队列就是系统为了方便程序员,专门提供的一种特殊的并发队列。

  • 全局队列和并发队列的区别

  • 全局队列:没有名称,无论ARC还是MRC都不需要考虑内存释放,日常开发,建议使用全局队列

  • 并发队列:
    (1)有名称,如果在MRC开发中,需要使用dispatch_release来释放相应的对象
    (2)dispatch_barrier 必须使用自定义的并发队列
    (3)开发第三方框架,建议使用并发队列

  • 第一个参数: identifier
    iOS7.0,表示的是优先级:

    DISPATCH_QUEUE_PRIORITY_HIGH = 2; 高优先级
    DISPATCH_QUEUE_PRIORITY_DEFAULT = 0; 默认优先级
    DISPATCH_QUEUE_PRIORITY_LOW = -2; 低优先级
    DISPATCH_QUEUE_PRIORITY_BACKGROUND = INT16_MIN; 后台优先级
iOS8.0开始,推荐使用服务质量(QOS):
    QOS_CLASS_USER_INTERACTIVE  = 0x21; 用户交互
    QOS_CLASS_USER_INITIATED    = 0x19; 用户期望
    QOS_CLASS_DEFAULT         = 0x15; 默认
    QOS_CLASS_UTILITY         = 0x11; 实用工具
    QOS_CLASS_BACKGROUND      = 0x09; 后台
    QOS_CLASS_UNSPECIFIED       = 0x00; 未指定

通过对比可知: 第一个参数传入0,可以同时适配iOS7及iOS7以后的版本。
服务质量和优先级是一一对应的:

    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
  • 第二个参数: flags 为未来保留使用的,始终传入0。

方法:

  1. 获取系统队列
    (1)获取主队列(一种串行队列) dispatch_queue_t 类型
dispatch_get_main_queue()

(2)获取全局队列(一种并发队列) dispatch_queue_t 类型

dispatch_get_global_queue(0, 0)
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
  1. 自定义创建一个串行、并发队列
    参数1:队列名
    参数2:队列类型,串行还是并发队列
    串行队列:DISPATCH_QUEUE_SERIAL 或 NULL
    并发队列:DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

例子:创建一个串行队列

dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("itheima", NULL);
  1. 创建任务
typedef void (^dispatch_block_t)(void);

例子:

dispatch_block_t task = ^{        
NSLog(@"hello %@",[NSThread currentThread]);
};
  1. 将任务添加到队列(参数1:队列 参数2:任务)
  • 同步函数,任务会在当前线程执行,因为同步函数不具备开新线程的能力。
    同步:你必须把我的代码执行完你再走,一定要执行完同步里的代码再执行下面的代码
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • 异步函数,任务会在子线程执行,因为异步函数具备开新线程的能力。
    异步:你先走执行我下面的代码,我找人、找线程去执行我里面的代码
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

例子:将任务同步添加到队列和将任务异步添加到队列

(1)dispatch_sync(queue, task);
(2)dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"hello %@",[NSThread currentThread]);
    });
  1. Barrier阻塞
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
  • 概念:会阻塞dispatch_barrier_async代码里的任务,让其它任务先执行完毕后再去执行里面的代码
  • 用处:
    (1)适合于大规模的 I/O 操作
    (2)当访问数据库或文件的时候,更新数据的时候不能和其他更新或读取的操作在同一时间执行,可以使用调度组不过有点复杂。可以使用dispatch_barrier_async解决。
  • 例子:执行结果十个图片下载完成先执行完毕,再执行十个保存图片
_queue = dispatch_queue_create("110", DISPATCH_QUEUE_CONCURRENT);
for (int i = 1; i<=10; i++) {    
[self downloadImage:i];}
-(void)downloadImage:(int)index {
    dispatch_async(_queue, ^{        //模拟下载图片        
    NSString *fileName = [NSString stringWithFormat:@"%02d.jpg",index % 10 + 1];        
    NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
    UIImage *img = [UIImage imageWithContentsOfFile:path];
        //等待队列中所有的任务执行完成(十个图片下载完成任务),才会执行barrier中的代码
        dispatch_barrier_async(_queue, ^{            
[self.photoList addObject:img];            
NSLog(@"保存图片 %@   %@",fileName,[NSThread currentThread]);
        }); 
        NSLog(@"图片下载完成 %@  %@",fileName,[NSThread currentThread]);
    }); 
}
  1. 延时操作
void dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
  • 参数1:dispatch_time_t when
    多少纳秒之后执行
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)
#define NSEC_PER_SEC 1000000000ull#define NSEC_PER_MSEC 1000000ull#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
  • 参数2:dispatch_queue_t queue
    任务添加到哪个队列
  • 参数3:dispatch_block_t block
    要执行的任务
    用法:延迟1秒后在主队列执行一个任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
  1. 一次性执行
  • 注意:一次性执行是线程安全的,只在当前线程上一次性执行
  • 步骤:
    (1)创建一个静态的 dispatch_once_t 变量;
    (2) dispatch_once(变量名, ^{
    //一次性执行语句
    });
  • 原理:判断静态的全局变量的值,默认是0,执行完成后变为-1
    dispatch_once内部会判断变量的值,如果是0才执行
  • 用处:可以使用一次性执行创建单例对象,效率比互斥锁高
  • 例子:
for (int i = 0; i<20000; i++) {    
static dispatch_once_t onceToken;    
dispatch_once(&onceToken, ^{        
NSLog(@"hello %@",[NSThread currentThread]);    });
}
  1. 调度组
  • 用处:有时候需要在多个异步任务都执行完成之后继续做某些事情,比如下载歌曲,等所有的歌曲都下载完毕之后再转到主线程提示用户
  • 步骤:
    (1)创建队列
    (2)创建调度组
dispatch_group_t group = dispatch_group_create();

(3)监听调度组内队列任务是否执行完毕:把任务添加到队列中,队列添加到调度组中,队列任务执行完毕通知调度组

void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

(4)接收到调度组执行完毕的通知后,执行其它任务

void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

例子:

//创建组
dispatch_group_t group = dispatch_group_create();
//队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//下载第一首歌曲
dispatch_group_async(group, queue, ^{
    NSLog(@"正在下载第一个歌曲");
});
//下载第二首歌曲
dispatch_group_async(group, queue, ^{    
NSLog(@"正在下载第二个歌曲");
    [NSThread sleepForTimeInterval:2.0];
});//下载第三首歌曲dispatch_group_async(group, queue, ^{    NSLog(@"正在下载第三个歌曲");
});
//当三个异步任务都执行完成,才执行dispatch_group_notify(group, dispatch_get_main_queue(), ^{    NSLog(@"over %@",[NSThread currentThread]);
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容