系列文章目录

GCD学习笔记<一>
GCD学习笔记<二>
GCD学习笔记<三>

Dispatch Group

在实际的开发过程中,我们不可避免地会遇见想要在多个并发操作都完成后执行某个处理,如果使用Concurrent Dispatch Queue或者多个Dispatch Queue会使得代码变得异常复杂。GCD给我们提供一种更加优雅的方式,即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, ^{
        NSLog(@"block 1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block 2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block 3");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"block done");
    });

结果如下

block 2
block 1
block 3
done

当然,前三个block的顺序是不确定的,但是done一定是最后一个输出。
假设我们仅仅想等待group的所有操作执行完呢?那么我们可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);dispatch_group_wait的第二个参数是超时事件,dispatch_time_t类型,这里使用DISPATCH_TIME_FOREVER意思是永久等待,但是需要注意一旦调用dispatch_group_wait,线程就处于被挂起的状态,一直等到group的所有操作C得到执行才会被继续执行。

dispatch_sync

我们最常用的是dispatch_async,但是我们还是有必要了解一下dispatch_sync,这个函数用的确实不是很多,它是将一个操作同步追加到Dispatch Queue中,在追加的操作执行完毕之前,dispatch_sync会像dispatch_group_wait一样出于等待状态。值得特别注意的是,千万千万不要在同一个线程进行sync,这样会造成线程死锁,如下:

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"Boom!");
        });
    });

执行上面代码,会让整个应用陷入死锁的状态。
SDWebImage中的SDWebImageCompat.h中定义了这样一个宏

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

一旦用到同步的时候,强烈建议大家先进行判断以防止发生死锁。

dispatch_apply

这个函数就比较简单了,它是是将block追加到指定Dispatch Queue指定次数,并等待全部block执行完毕,换句话说,它用的其实是dispatch_sync

    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
4
2
3
6
7
9
5
8
done

我们采用的是Global Dispatch Queue,所以各个操作执行的顺序不定,但是done一定在最后。
那么这个函数的主要用图可以让我们并发的处理某些数组,值得注意的是,由于dispatch_apply是同步的,所以在真正使用过程中,我们通常会使用dispatch_async非同步地执行dispatch_apply,示例如下:

    NSArray *items = @[@11,@22,@33,@44,@55];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        dispatch_apply([items count], queue, ^(size_t index) {
            NSLog(@"%zu: %@",index,items[index]);
        });

        NSLog(@"done");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"update UI here.");
        });
    });

dispatch_suspend && dispatch_resume

我们有时候需要暂停某个Dispatch Queue,等到适宜的时候再进行恢复执行,这时我们就用到了dispatch_suspend && dispatch_resume这两个方法。用法也很简单:
挂起:

    dispatch_suspend(queue);

恢复

    dispatch_resume(queue);

dispatch_once

这个应该是大家都比较常用的方法,dispatch_once方法可以保证在应用程序中只执行一次,即使是多线程的环境下,也可保证百分之百的安全。
我们通常用它来实现单例,下面这种就是教科书似的写法,没什么好解释的:

+ (id)sharedInstance {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

dispatch_block_cancel

GCD一直被诟病的地方就是取消操作,通常情况下,我们的操作有可能被取消时,我们都尽量选用NSOperation/NSOperationQueue,如果非要作大死其实也不难,无非就是自己加个BOOL变量,执行前判断一下就可以了,如果想在执行过程中也可取消,也可以多判断几次。iOS 8开放了一个新的API,给取消block带来极大的便捷,然而只支持到iOS 8,当然这个API也是有局限性的,也就是仅仅能取消在队列里还未执行的block,对正在执行的block是无效的。
示例代码如下:

    dispatch_queue_t queue =         dispatch_queue_create("xyz.ypli.CLearn.SerialDispatchQueue",  DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block2 = dispatch_block_create(0, ^{
        NSLog(@"block 2");
    });

    dispatch_async(queue, ^{
        NSLog(@"block 1 start");
        sleep(2);
        NSLog(@"block 1 end");
    });
    dispatch_async(queue, block2);

    dispatch_async(queue, ^{
        NSLog(@"block 3");
    });

    dispatch_block_cancel(block2);

这里还有一个坑,想使用这个函数的话,block必须通过dispatch_block_create函数进行创建,不然程序就会挂掉。