GCD的常见使用

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


#define DISPATCH_QUEUE_PRIORITY_HIGH 2  最高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认优先级在所有高优先级队列都被安排之后,但是在任何低优先级队列被调度之前。
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 发送到队列的将以低优先级运行,即,队列最终将被安排执行默认优先级和高先级队列已经存在。计划。
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 分配给队列的项将以后台优先级(即队列)运行将在所有优先级较高的队列被调度后进行执行(这是最低的优先级)

iOS8.0之后的权限
QOS_CLASS_USER_INTERACTIVE  与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
QOS_CLASS_USER_INITIATED    由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
QOS_CLASS_DEFAULT   优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
QOS_CLASS_UTILITY   一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
QOS_CLASS_BACKGROUND    这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
QOS_CLASS_UNSPECIFIED   未指定

创建一个队列在不同版本有不同的选择

 // 如果iOS 8和以上版本的话,创建queue的方法和之前的版本的不太太一样。在iOS 8和以上的版本中创建queue需要先创建一个dispatch_queue_attr_t类型的实例。并作为参数传入到queue的生成方法里。
    dispatch_queue_t queue;
    if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
        dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
        queue = dispatch_queue_create("com.tao.render", attr);
    } else {
        queue = dispatch_queue_create("com.tao.render", DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
    }

一、串行队列
Serial Diapatch Queue 串行队列
当任务相互依赖,具有明显的先后顺序的时候,使用串行队列是一个不错的选择
创建一个串行队列:

- (void)dispatchSerialqueueTest {
    ///第一个参数为队列名,第二个参数为队列类型,当然,第二个参数人如果写NULL,创建出来的也是一个串行队列。然后我们在异步线程来执行这个队列
    dispatch_queue_t  serialQueue = dispatch_queue_create("com.tao.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"serial 1");
        sleep(1);
    });
    dispatch_async(serialQueue, ^{
        sleep(2);
        NSLog(@"serial 2");
    });
    
    dispatch_async(serialQueue, ^{
        sleep(2);
        NSLog(@"serial 3");
    });
}

二、并发队列

Concurrent Diapatch Queue 并发队列
与串行队列刚好相反,他不会存在任务间的相互依赖。

- (void)dispatchConcurrentQueueTest {
    
    dispatch_queue_t  concurrentQueue = dispatch_queue_create("com.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        sleep(2);
        NSLog(@"2");
    });
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"3");
    });
    
}

三、系统队列
Global Queue & Main Queue
这是系统为我们准备的2个队列:

Global Queue 其实就是系统创建的Concurrent Diapatch Queue (并发)
Main Queue 其实就是系统创建的位于主线程的Serial Diapatch Queue (串行)
下面也测试了之前说的一些队列优先级

- (void)dispatchGlobalAndMainQueue {
    /***
     异步主线程
     
     在日常工作中,除了在其他线程返回主线程的时候需要用这个方法,还有一些时候我们在主线程中直接调用异步主线程,这是利用dispatch_async的特性:block中的任务会放在主线程本次runloop之后返回。这样,有些存在先后顺序的问题就可以得到解决了。
     **/
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"异步线程");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"异步主线程");
        });
    });
    
  /****  dispatch_get_global_queue存在优先级,没错,他一共有4个优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
    **/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"4");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        NSLog(@"3");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"1");
    });
}

四、队列优先级的操控
dispatch_set_target_queue 操作队列优先级方法
我把自己创建的队列塞到了系统提供的global_queue队列中,我们可以理解为:我们自己创建的queue其实是位于global_queue中执行,所以改变global_queue的优先级,也就改变了我们自己所创建的queue的优先级。所以我们常用这种方式来管理子队列。
根据(先进先出原则 FIFO)

- (void)dispatchSetTargetTest {
    dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", NULL);
    dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    dispatch_set_target_queue(dispatchgetglobalqueue,serialDiapatchQueue);///先进先出原则,先调用后面的,在调用前面的
    dispatch_async(serialDiapatchQueue, ^{
        NSLog(@"我优先级低,先让让");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"我优先级高,我先block");
    });
}

五、延迟调用方法 定时器
单次执行定时器
dispatch_after
这个是最常用的,用来延迟执行的GCD方法,因为在主线程中我们不能用sleep来延迟方法的调用,所以用它是最合适的,

我们看到他就是在主线程,就是刚好延迟了2秒,当然,我说这个2秒并不是绝对的,为什么这么说?dispatch_async的block中的方法执行会放在主线程runloop之后,所以,如果此时runloop周期较长的时候,可能会有一些时差产生。

多次执行定时器
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);// 建立一个定时器对象
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);// 设置定时器相关参数
dispatch_source_set_event_handler(_timer, ^{ //定时器执行事件}
使用注意:需要把_timer 申请为全局变量,不然的话,会导致,_timer被释放,从而导致定时器不触发

- (void)dispatchAfterTest {
    // 单次定时器
    NSLog(@"单次定时器前");
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSLog(@"单次定时器后");
    });
// 多次定时器使用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 设置默认优先级
    NSTimeInterval period = 1.0; //设置时间间隔
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //第一个参数 dispatch_source_t 对象 第二个参数 延迟多少秒开始执行 第三个参数 每秒执行  最后一个参数,允许误差多少秒
    
    dispatch_source_set_event_handler(_timer, ^{    //在这里执行事件
        timeInt ++;
        NSLog(@"*******************定时器来了**********");
        if (timeInt == 5) {
            NSLog(@"定时器暂停");
            dispatch_suspend(_timer); //
            sleep(2);
            dispatch_resume(_timer);
            NSLog(@"定时器恢复");
        }
        if (timeInt == 10) {
            dispatch_source_cancel(_timer);
            _timer = nil;
            NSLog(@"定时器消耗");
        }
        
    });
    dispatch_resume(_timer);


}

六、任务组的使用
dispatch_group

当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。

- (void)dispatchGruopQueueTest {
    
    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, ^{NSLog(@"组0");});
    dispatch_group_async(group, queue, ^{NSLog(@"组1");});
    dispatch_group_async(group, queue, ^{NSLog(@"组2");});
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"gruop 组执行完毕");});
}

七、打断任务,插入任务
dispatch_barrier_async

此方法的作用是在并发队列中,完成在它之前提交到队列中的任务后打断,
单独执行其block,
并在执行完成之后才能继续执行在他之后提交到队列中的任务:

- (void)dispatchBarrierQueueTest {
    dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"0");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"1");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"2");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"3");});
    dispatch_barrier_async(concurrentDiapatchQueue, ^{sleep(1); NSLog(@"打断了。。。。");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"5");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"6");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"7");});
    dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"8");});
}

八、无序查找
dispatch_apply

这个方法用于无序查找,在一个数组中,我们能开启多个线程来查找所需要的值,
但是在遍历完成之前会阻塞主线程

- (void)dispathchApplyTest {
    
    NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
    });

    NSLog(@"阻塞");// 将会在便利之后完成之后打印
}

九、队列的挂起,恢复
dispatch_suspend & dispatch_resume

队列挂起和恢复,

- (void)dispatchSuspendAndResumeTest {
    
    dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.suspend_resume.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentDiapatchQueue, ^{
        for (int i=0; i<100; i++)
        {
            NSLog(@"%i",i);
            if (i==50)
            {
                NSLog(@"-----------------------------------队列挂起");
                dispatch_suspend(concurrentDiapatchQueue);
                sleep(3);
                dispatch_async(dispatch_get_main_queue(), ^{
                    dispatch_resume(concurrentDiapatchQueue);
                    NSLog(@"-----------------------------------队列恢复");
                });
            }
            
        }
    });
}

十、信号量
Semaphore 有三个方法
创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)

等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)

提高信号量
dispatch_semaphore_signal(信号量)
信号量的使用
举个例子
我们要下载很多文件,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。
用好信号量,可以达到合理分配CPU资源,程序也能得到优化。

- (void)dispatchSignalTest {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//为了让一次下载10个,初始信号量为10
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i <100; i++)
    {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//每进来1次,信号量-1;进来10次后就一直hold住,直到信号量大于0;
        dispatch_async(queue, ^{
            NSLog(@"下载文件_%i",i);
            sleep(2);
            dispatch_semaphore_signal(semaphore);//由于这里只是log,所以处理速度非常快,我就模拟2秒后下载完成并信号量+1;
        });
    }

}

十一、单列
dispatch_once
这个函数一般是用来做一个真的单例,也是非常常用的
但是使用单列需要小心,否则会造成线程死锁
单列 A B C D
引用关系
A->B B->A 这样就会死锁,但是这种错误一般大家都不会发生,但是如果单列多了,可能就是下面这种情况,而且有时候你自己也不知道
A->B ->C->D ->A 这种情况就会造成单列死锁,所以,各位同学使用单列的时候,需要注意哦😁

+ (ViewController *)dispatchOnceTest {
    static ViewController * instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc]init];
    });
    return instance;
}

以上是本人的浅见,如有错误,望各位多多指正😘

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

推荐阅读更多精彩内容