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

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

你可能感兴趣的:(GCD的常见使用)