iOS中的GCD使用总结

iOS中的GCD使用总结

GCD是iOS开发者必须要熟练掌握和使用的异步执行任务的技术之一。下面是我自己总结的GCD的使用总结。

GCD的API

Dispatch Queue

GCD中,执行时存在2种Dispatch Queue。一种是串行队列Serial Dispatch Queue,一种是并发队列Concurrent Dispatch Queue。我们可以自己创建串行队列和并发队列:

// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);

 //并发队列
 dispatch_queue_t concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);

串行队列Serial Dispatch Queue是指执行多个任务时,任务会按照先后顺序一个一个执行,也就是说Serial Dispatch Queue同时只能执行一个任务。注意:使用dispatc_queue_create函数时,第二个参数如果不指定为DISPATCH_QUEUE_SERIAL或者DISPATCH_QUEUE_CONCURRENT,传入NULL则为串行队列。

虽然如此,你却可以创建多个Serial Dispatch Queue队列,此时各个Serial Dispatch Queue将并行执行。比如你可以通过dispatch_queue_create同时创建5个串行队列,然后再在每个串行队列中执行一个任务,那么CPU将同时执行这5个任务。

但是,如果我们创建过多的线程,就会造成大量的性能开销,造成CPU的频繁调度,反而会降低系统的性能。

Dispatch Main Queue

我们可以获取系统为我们提供的Dispatch Queue。比如Main Dispatch Queue和Global Dispatch Queue。

// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

Main Dispatch Queue就是我们说的主线程或者UI线程。因为主线程只有一个,Main Dispatch Queue自然就是串行队列Serial Dispatch Queue。

Global Dispatch Queue

系统为我们提供了并发队列Concurrent Dispatch Queue:

// 全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

Global Dispatch Queue共提供了4个优先级。DISPATCH_QUEUE_PRIORITY_HIGH:高优先级;DISPATCH_QUEUE_PRIORITY_DEFAULT:默认优先级;DISPATCH_QUEUE_PRIORITY_LOW:低优先级;DISPATCH_QUEUE_PRIORITY_BACKGROUND: 后台。

声明的宏定义如下:

#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_set_target_queue

我们通过dispatch_queue_create创建生成的队列,默认使用的优先级都是与dispatch_get_global_queue使用DISPATCH_QUEUE_PRIORITY_DEFAULT的优先级相同。而如果你想要改变自己创建的队列的优先级,需要使用dispatch_set_target_queue函数来实现:

// 自己创建的队列
dispatch_queue_t serialQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// 使用系统提供的队列,并指定优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t  globalBackgoundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//我们希望自己创建的队列也使用与DISPATCH_QUEUE_PRIORITY_BACKGROUND相同的优先级,可以这样指定:
// (原队列, 参考的队列)
 dispatch_set_target_queue(serialQueue, globalBackgoundQueue);

请不要将原队列设置为主队列Main Queue和全局队列Global Queue。

通过dispatch_get_global_queue不仅可以改变队列的优先级,还可以变更队列的“执行阶层”。我们创建5个串行队列,然后异步执行:

    dispatch_queue_t serialQueue1 = dispatch_queue_create("liuxingxing1", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("liuxingxing2", NULL);
    dispatch_queue_t serialQueue3 = dispatch_queue_create("liuxingxing3", NULL);
    dispatch_queue_t serialQueue4 = dispatch_queue_create("liuxingxing4", NULL);
    dispatch_queue_t serialQueue5 = dispatch_queue_create("liuxingxing5", NULL);
    dispatch_async(serialQueue1, ^{
        NSLog(@"1:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue2, ^{
        NSLog(@"2:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue3, ^{
        NSLog(@"3:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue4, ^{
        NSLog(@"4:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue5, ^{
        NSLog(@"5:%@",[NSThread currentThread]);
    });

打印结果如下:

2018-08-02 09:40:14.540502+0800 GCD[77612:10924290] 2:<NSThread: 0x6000034e7240>{number = 5, name = (null)}
2018-08-02 09:40:14.540506+0800 GCD[77612:10924287] 1:<NSThread: 0x6000034cb240>{number = 6, name = (null)}
2018-08-02 09:40:14.540588+0800 GCD[77612:10924291] 5:<NSThread: 0x6000034e6c40>{number = 3, name = (null)}
2018-08-02 09:40:14.540544+0800 GCD[77612:10924293] 4:<NSThread: 0x6000034cb280>{number = 7, name = (null)}
2018-08-02 09:40:14.540644+0800 GCD[77612:10924298] 3:<NSThread: 0x6000034cb300>{number = 8, name = (null)}

通过打印,我们可以看到,系统为5个队列分别开了5个线程,5个线程并行执行,没有先后顺序。

现在,我们再创建一个串行队列,让这5个串行队列作为该队列的执行阶层:

    // 目标串行队列
    dispatch_queue_t targetSerialQueue = dispatch_queue_create("liuxingxing", NULL);
    // 5个用于测试的串行队列
    dispatch_queue_t serialQueue1 = dispatch_queue_create("liuxingxing1", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("liuxingxing2", NULL);
    dispatch_queue_t serialQueue3 = dispatch_queue_create("liuxingxing3", NULL);
    dispatch_queue_t serialQueue4 = dispatch_queue_create("liuxingxing4", NULL);
    dispatch_queue_t serialQueue5 = dispatch_queue_create("liuxingxing5", NULL);
    // 将这5个队列设置为目标队列的执行阶层:
    dispatch_set_target_queue(serialQueue1, targetSerialQueue);
    dispatch_set_target_queue(serialQueue2, targetSerialQueue);
    dispatch_set_target_queue(serialQueue3, targetSerialQueue);
    dispatch_set_target_queue(serialQueue4, targetSerialQueue);
    dispatch_set_target_queue(serialQueue5, targetSerialQueue);
    NSLog(@"执行了。。。");
    dispatch_async(serialQueue1, ^{
        NSLog(@"1:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue2, ^{
        NSLog(@"2:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue3, ^{
        NSLog(@"3:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue4, ^{
        NSLog(@"4:%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue5, ^{
        NSLog(@"5:%@",[NSThread currentThread]);
    });
    NSLog(@"我也执行了。。。");

打印结果如下:

2018-08-02 09:54:08.938848+0800 GCD[78218:10933655] 执行了。。。
2018-08-02 09:54:08.939075+0800 GCD[78218:10933655] 我也执行了。。。
2018-08-02 09:54:08.939155+0800 GCD[78218:10933741] 1:<NSThread: 0x6000004e0180>{number = 5, name = (null)}
2018-08-02 09:54:08.939307+0800 GCD[78218:10933741] 2:<NSThread: 0x6000004e0180>{number = 5, name = (null)}
2018-08-02 09:54:08.939431+0800 GCD[78218:10933741] 3:<NSThread: 0x6000004e0180>{number = 5, name = (null)}
2018-08-02 09:54:08.939567+0800 GCD[78218:10933741] 4:<NSThread: 0x6000004e0180>{number = 5, name = (null)}
2018-08-02 09:54:08.939698+0800 GCD[78218:10933741] 5:<NSThread: 0x6000004e0180>{number = 5, name = (null)}

我们可以发现,5个串行队列在一个编号为5的子线程中异步串行执行了。

dispatch_after

我们有时候需要延迟指定时间后做执行的场景。假如你希望延迟5s后执行处理,可以这样实现:

    NSLog(@"执行了。。。");
    dispatch_time_t afterTime = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);
    dispatch_after(afterTime, dispatch_get_main_queue(), ^{
        NSLog(@"我5秒后执行了。。。");
    });
    NSLog(@"我也执行了。。。");

补充:5ull * NSEC_PER_SEC的乘积结果是单位为毫微秒的数值。“ull”是C语言的数值字面量,是显式表明类型时使用的字符串(表示“unsigned long long”)。如果使用NSEC_PER_MSEC,则表示已毫秒计算。如果不是很清晰,我们可以点击这个宏,就一目了然了。

#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull

#define DISPATCH_TIME_NOW (0ull)

接下来,我们查看打印结果,结果如下:

2018-08-02 10:16:51.485556+0800 GCD[79047:10947179] 执行了。。。
2018-08-02 10:16:51.485787+0800 GCD[79047:10947179] 我也执行了。。。
2018-08-02 10:16:56.486077+0800 GCD[79047:10947179] 我5秒后执行了。。。

需要注意的是,5s后执行是指5s后将block中的业务处理添加到mainQueue中,而不是说5s后执行block中的业务代码。也就是说,如果主队列mainQueue中有大量耗时操作时,虽然5s时追加到了主队列,但这个实际执行的时间要比5s时间长的多。

我们一般会使用dispatch_time函数来计算相对时间。但是我们如果想使用绝对时间,例如在“2018-08-02 11:19:00”时执行,可以使用**struct** timespec来实现:

dispatch_time_t getDispatchTimeByDate(NSDate *date) {
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time, 0);
    return milestone;
}

调用该函数:

    NSLog(@"执行了。。。");
    NSDateFormatter * form = [[NSDateFormatter alloc]init];
    form.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSDate *date = [form dateFromString:@"2018-08-02 11:19:00"];
    dispatch_after(getDispatchTimeByDate(date), dispatch_get_main_queue(), ^{
        NSLog(@"我在2018-08-02 11:19:00秒执行了。。。");
    });
    NSLog(@"我也执行了。。。");

打印结果:

2018-08-02 11:18:08.710400+0800 GCD[81313:10983251] 执行了。。。
2018-08-02 11:18:08.724402+0800 GCD[81313:10983251] 我也执行了。。。
2018-08-02 11:19:00.001673+0800 GCD[81313:10983251] 我在2018-08-02 11:19:00秒执行了。。。

Dispatch Group

如果我们使用异步并发队列执行多个耗时操作,执行完毕后希望在主队列main Queue中再做下一步的处理。这时候你可以使用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(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"我也执行了。。。");
    });

执行结果如下:

2018-08-02 11:49:09.194348+0800 GCD[82438:11001111] 执行了1:<NSThread: 0x6000007916c0>{number = 4, name = (null)}
2018-08-02 11:49:09.194348+0800 GCD[82438:11001100] 执行了3:<NSThread: 0x60000079fe40>{number = 7, name = (null)}
2018-08-02 11:49:09.194348+0800 GCD[82438:11001102] 执行了2:<NSThread: 0x600000791980>{number = 5, name = (null)}
2018-08-02 11:49:09.212106+0800 GCD[82438:11000996] 我也执行了。。。

无论子队列按照什么顺序执行,主队列都会在所有子队列执行完毕后再做处理。

dispatch_group_wait

我们也可以在Dispatch Group中使用dispatch_group_wait函数,dispatch_group_wait可以指定超时的时间。例如,我们的超时时间为5s,超过5s,则提示用户失败:

    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(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,  5ull * NSEC_PER_SEC);
    long result = dispatch_group_wait(group,time);
    if (result == 0) {
        NSLog(@"5s内执行完毕了。。。");
    } else {
        NSLog(@"5s内还没有执行完!!!!");
    }

如果dispatch_group_wait的返回值result不为0,就意味着在指定时间内,Dispatch Group中还有处理没有完成;如果返回值为0,表示已经在指定时间内完成。

如果设置的超时时间为DISPATCH_TIME_FOREVER,那么就意味着会一直等待,直到Dispatch Group完成。所以dispatch_group_wait的返回值result将一直为0。如果指定超时时间为DISPATCH_TIME_NOW,则不设置超时时间,立即判断Dispatch Group是否执行结束。

#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)

dispatch_barrier_async

dispatch_barrier_async可以想象为一个栅栏,在一个异步并发的多线程任务中,执行顺序是不固定的:


    NSLog(@"执行了。。。");
    dispatch_queue_t queue = dispatch_queue_create("liuxingxing", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了4:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了5:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了6:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了7:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了8:%@",[NSThread currentThread]);
    });
     NSLog(@"我也执行了。。。");

打印结果如下:

2018-08-02 18:56:09.165354+0800 GCD[1349:11264928] 执行了。。。
2018-08-02 18:56:09.167767+0800 GCD[1349:11264928] 我也执行了。。。
2018-08-02 18:56:09.168206+0800 GCD[1349:11265364] 执行了1:<NSThread: 0x600000d8cf00>{number = 15, name = (null)}
2018-08-02 18:56:09.169986+0800 GCD[1349:11265364] 执行了2:<NSThread: 0x600000d8cf00>{number = 15, name = (null)}
2018-08-02 18:56:09.171324+0800 GCD[1349:11265596] 执行了4:<NSThread: 0x600000d71b00>{number = 18, name = (null)}
2018-08-02 18:56:09.171176+0800 GCD[1349:11265594] 执行了3:<NSThread: 0x600000d9de00>{number = 17, name = (null)}
2018-08-02 18:56:09.171441+0800 GCD[1349:11265364] 执行了5:<NSThread: 0x600000d8cf00>{number = 15, name = (null)}
2018-08-02 18:56:09.171562+0800 GCD[1349:11265596] 执行了8:<NSThread: 0x600000d71b00>{number = 18, name = (null)}
2018-08-02 18:56:09.171559+0800 GCD[1349:11265594] 执行了7:<NSThread: 0x600000d9de00>{number = 17, name = (null)}
2018-08-02 18:56:09.171611+0800 GCD[1349:11265598] 执行了6:<NSThread: 0x600000d71580>{number = 19, name = (null)}

现在,我们希望在执行线程3和执行线程4之间做些操作,就可以使用dispatch_barrier_async函数。

    NSLog(@"执行了。。。");
    dispatch_queue_t queue = dispatch_queue_create("liuxingxing", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"我是3和4之间的dispatch_barrier_async");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了4:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了5:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了6:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了7:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了8:%@",[NSThread currentThread]);
    });
     NSLog(@"我也执行了。。。");

打印结果如下:

2018-08-02 19:01:25.175002+0800 GCD[1389:11268238] 我也执行了。。。
2018-08-02 19:01:25.176161+0800 GCD[1389:11268735] 执行了1:<NSThread: 0x600000edf840>{number = 10, name = (null)}
2018-08-02 19:01:25.176187+0800 GCD[1389:11268826] 执行了2:<NSThread: 0x600000edfcc0>{number = 11, name = (null)}
2018-08-02 19:01:25.176276+0800 GCD[1389:11268827] 执行了3:<NSThread: 0x600000edff00>{number = 12, name = (null)}
2018-08-02 19:01:25.176630+0800 GCD[1389:11268827] 我是3和4之间的dispatch_barrier_async
2018-08-02 19:01:25.176820+0800 GCD[1389:11268826] 执行了5:<NSThread: 0x600000edfcc0>{number = 11, name = (null)}
2018-08-02 19:01:25.176782+0800 GCD[1389:11268827] 执行了4:<NSThread: 0x600000edff00>{number = 12, name = (null)}
2018-08-02 19:01:25.176936+0800 GCD[1389:11268735] 执行了6:<NSThread: 0x600000edf840>{number = 10, name = (null)}
2018-08-02 19:01:25.177329+0800 GCD[1389:11268828] 执行了7:<NSThread: 0x600000ed1f40>{number = 13, name = (null)}
2018-08-02 19:01:25.177525+0800 GCD[1389:11268829] 执行了8:<NSThread: 0x600000ed1d40>{number = 14, name = (null)}

可以看到,dispatch_barrier_async就好比一个栅栏,栅栏两侧的线程无论什么执行顺序,但是dispatch_barrier_async会始终“横亘”在线程3和线程4之间。

dispatch_sync

dispatch_async与dispatch_sync两者的区别是:dispatch_async是异步队列,所谓异步,就是指不会等待block块的执行完毕才会执行后面的代码;dispatch_sync则恰恰相反,表示是同步队列,表示在block代码快执行完毕之前,不会执行后面的代码。

我们先看dispatch_async异步执行的代码:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_queue_create("liuxingxing", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
     NSLog(@"end。。。");

dispatch_async的执行结果是这样的:

2018-08-02 19:11:10.560857+0800 GCD[1490:11273203] start。。。
2018-08-02 19:11:10.561109+0800 GCD[1490:11273203] end。。。
2018-08-02 19:11:10.561169+0800 GCD[1490:11273294] 执行了1:<NSThread: 0x6000005ef400>{number = 3, name = (null)}
2018-08-02 19:11:10.561225+0800 GCD[1490:11273293] 执行了3:<NSThread: 0x60000058b040>{number = 6, name = (null)}
2018-08-02 19:11:10.561225+0800 GCD[1490:11273297] 执行了2:<NSThread: 0x6000005b5740>{number = 5, name = (null)}

我们可以看到,“start”和“end”按照代码执行顺序分别为开始和结束,但是异步函数却没有等待block中的代码执行完毕后再执行end.

dispatch_sync的测试代码如下:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_queue_create("liuxingxing", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"执行了1:%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"执行了2:%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"执行了3:%@",[NSThread currentThread]);
    });
     NSLog(@"end。。。");

dispatch_sync的执行结果是这样的:

2018-08-02 19:20:51.028573+0800 GCD[1642:11279240] start。。。
2018-08-02 19:20:51.028822+0800 GCD[1642:11279240] 执行了1:<NSThread: 0x6000018918c0>{number = 1, name = main}
2018-08-02 19:20:51.028980+0800 GCD[1642:11279240] 执行了2:<NSThread: 0x6000018918c0>{number = 1, name = main}
2018-08-02 19:20:51.029108+0800 GCD[1642:11279240] 执行了3:<NSThread: 0x6000018918c0>{number = 1, name = main}
2018-08-02 19:20:51.029208+0800 GCD[1642:11279240] end。。。

虽然我们使用了并发队列,但是由于我们使用的同步操作,可以看到,系统并没有开启子线程,并且,始终按照代码的顺序进行执行的。

需要注意的是,我们使用dispatch_sync函数时,一定要不要为dispatch_sync指定为Main Queue主队列,否则将会发生死锁:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行了1:%@",[NSThread currentThread]);
    });

     NSLog(@"end。。。");

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

同样的,下面的这种写法,也会造成死锁:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
       dispatch_sync(queue, ^{
            NSLog(@"执行了1:%@",[NSThread currentThread]);
        });
    });

     NSLog(@"end。。。");

dispatch_apply

我们可以使用dispatch_apply来实现在指定线程内多次执行任务:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"当前线程为:%@---%zu", [NSThread currentThread],index);
    });
     NSLog(@"end。。。");

打印结果如下:

2018-08-02 19:35:04.326280+0800 GCD[1849:11288245] start。。。
2018-08-02 19:35:04.326591+0800 GCD[1849:11288245] 当前线程为:<NSThread: 0x600000630740>{number = 1, name = main}---0
2018-08-02 19:35:04.327070+0800 GCD[1849:11288245] 当前线程为:<NSThread: 0x600000630740>{number = 1, name = main}---1
2018-08-02 19:35:04.327574+0800 GCD[1849:11288378] 当前线程为:<NSThread: 0x600000660080>{number = 3, name = (null)}---3
2018-08-02 19:35:04.327553+0800 GCD[1849:11288245] 当前线程为:<NSThread: 0x600000630740>{number = 1, name = main}---2
2018-08-02 19:35:04.327740+0800 GCD[1849:11288378] 当前线程为:<NSThread: 0x600000660080>{number = 3, name = (null)}---4
2018-08-02 19:35:04.328556+0800 GCD[1849:11288245] end。。。

我们可以通过这种方式,来模仿系统对NSArray的实现:

    NSArray *testArray = @[@"a",@"b",@"c",@"d",@"e"];
    [testArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
         NSLog(@"当前元素为:%@---当前下标为%zd", obj,idx);
    }];
    //自己模拟实现
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(testArray.count, queue, ^(size_t index) {
        NSLog(@"当前元素为:%@", testArray[index]);
    });

dispatch_suspend和dispatch_resume

GCD为我们提供了“挂起”、“恢复”队列的功能。但是,需要注意的是,“挂起”的队列是针对尚未执行的block,正在执行的block不会被挂起。

为了演示这个功能,需要创建一个串行异步队列做演示,然后创建3个耗时任务,每个耗时任务需要时长5s:

dispatch_queue_t queue;
bool flag = 1;
- (void)viewDidLoad {
    [super viewDidLoad];
    [self test];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (flag == 1) {
        NSLog(@"暂停任务");
        flag = 0;
        dispatch_suspend(queue);
    } else {
        flag = 1;
        NSLog(@"继续任务");
         dispatch_resume(queue);
    }

}
- (void)test {
    NSLog(@"start");
    queue = dispatch_queue_create("liuxingxing", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
         NSLog(@"我开始执行任务1");
        sleep(5);
        NSLog(@"任务1执行完毕");
    });
    dispatch_async(queue, ^{
        NSLog(@"我开始执行任务2");
        sleep(5);
        NSLog(@"任务2执行完毕");
    });
    dispatch_async(queue, ^{
        NSLog(@"我开始执行任务3");
        sleep(5);
        NSLog(@"任务3执行完毕");
    });
}

我们在2s左右切换一下线程的状态,打印结果如下:

2018-08-03 15:05:30.636230+0800 GCD[20645:11727750] start
2018-08-03 15:05:30.636592+0800 GCD[20645:11727858] 我开始执行任务1
2018-08-03 15:05:32.325692+0800 GCD[20645:11727750] 暂停任务
2018-08-03 15:05:35.640567+0800 GCD[20645:11727858] 任务1执行完毕
2018-08-03 15:05:44.397150+0800 GCD[20645:11727750] 继续任务
2018-08-03 15:05:44.397735+0800 GCD[20645:11727856] 我开始执行任务2
2018-08-03 15:05:45.991072+0800 GCD[20645:11727750] 暂停任务
2018-08-03 15:05:49.402791+0800 GCD[20645:11727856] 任务2执行完毕
2018-08-03 15:05:53.625076+0800 GCD[20645:11727750] 继续任务
2018-08-03 15:05:53.625362+0800 GCD[20645:11727856] 我开始执行任务3
2018-08-03 15:05:54.609694+0800 GCD[20645:11727750] 暂停任务
2018-08-03 15:05:58.629619+0800 GCD[20645:11727856] 任务3执行完毕

通过打印结果可以验证,我们暂停任务时,正在执行的任务并不会暂停,而是会执行完毕,被“挂起”的任务是没有被执行的任务。而恢复,则使这些被挂起的任务继续执行。

Dispatch Semaphore

GCD为我们提供了信号量机制,让我们可以进行更加细度的控制。信号量机制主要涉及到下面三个函数:

dispatch_semaphore_create(long value); // 创建信号量
dispatch_semaphore_signal(dispatch_semaphore_t deem); // 发送信号量
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待信号量

dispatch_semaphore_create用来创建信号量。value表示可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。我们也可以把这个value当做并发总数量,dispatch_semaphore_wait会使信号量的资源数减去1,可以理解为value - 1 ;dispatch_semaphore_signal发送信号量,可以理解为添加信号量,也就是value + 1。

使用场景1:解决死亡回调

假设我们有3个网络接口ABC,B接口依赖A,C依赖B,如果我们要实现这个功能,最简单粗暴的办法就是:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{ //模拟网络请求1
        NSLog(@"我开始执行任务1");
        sleep(5);
        NSLog(@"任务1执行完毕");
        //模拟1成功的回调

        dispatch_async(queue, ^{ //模拟网络请求2
            NSLog(@"我开始执行任务2");
            sleep(5);
            NSLog(@"任务2执行完毕");

            //模拟2成功的回调
            dispatch_async(queue, ^{  //模拟网络请求3
                NSLog(@"我开始执行任务3");
                sleep(5);
                NSLog(@"任务3执行完毕");
            });
        });
    });
    NSLog(@"end。。。");

打印结果如下:

2018-08-03 19:28:55.177312+0800 GCD[31913:11879778] start。。。
2018-08-03 19:28:55.177565+0800 GCD[31913:11879778] end。。。
2018-08-03 19:28:55.177603+0800 GCD[31913:11879878] 我开始执行任务1
2018-08-03 19:29:00.178352+0800 GCD[31913:11879878] 任务1执行完毕
2018-08-03 19:29:00.178604+0800 GCD[31913:11879878] 我开始执行任务2
2018-08-03 19:29:05.182333+0800 GCD[31913:11879878] 任务2执行完毕
2018-08-03 19:29:05.182579+0800 GCD[31913:11879878] 我开始执行任务3
2018-08-03 19:29:10.186252+0800 GCD[31913:11879878] 任务3执行完毕

也就是,我们在网络成功的回调中,再次嵌套网络回调。如果多个网络请求相互依赖,就会陷入死亡回调。

我们可以使用GCD提供的信号量机制,相对优雅的解决这个问题:

    NSLog(@"start。。。");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //
    dispatch_async(queue, ^{ //模拟网络请求1
        NSLog(@"我开始执行任务1");
        sleep(5);
        NSLog(@"任务1执行完毕");
        //模拟1成功的回调
        // 发送信号量
         dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{ //模拟网络请求1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"我开始执行任务2");
        sleep(5);
        NSLog(@"任务2执行完毕");
        //模拟2成功的回调
        // 发送信号量
         dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{ //模拟网络请求1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"我开始执行任务3");
        sleep(5);
        NSLog(@"任务3执行完毕");
    });
    NSLog(@"end。。。");

打印结果如下:

2018-08-03 19:34:32.836136+0800 GCD[32019:11883248] start。。。
2018-08-03 19:34:32.836377+0800 GCD[32019:11883248] end。。。
2018-08-03 19:34:32.836391+0800 GCD[32019:11883361] 我开始执行任务1
2018-08-03 19:34:37.841238+0800 GCD[32019:11883361] 任务1执行完毕
2018-08-03 19:34:37.841505+0800 GCD[32019:11883362] 我开始执行任务2
2018-08-03 19:34:42.845288+0800 GCD[32019:11883362] 任务2执行完毕
2018-08-03 19:34:42.845526+0800 GCD[32019:11883360] 我开始执行任务3
2018-08-03 19:34:47.848411+0800 GCD[32019:11883360] 任务3执行完毕

dispatch_once

dispatch_once函数可以保证在应用程序执行中只执行一次处理。我们最可能想到的是单例:

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

    });

AFNetworking中使用dispatch_once函数:

+ (instancetype)sharedManager {
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [self manager];
    });

    return _sharedManager;
}

   转载规则


《iOS中的GCD使用总结》 刘星星 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
iOS中的内存管理总结 iOS中的内存管理总结
内存管理iOS通过以下三种方式来管理内存方案: TaggedPointer //非指针型的isa NONPOINTER_ISA //包含了引用计数表 和 弱引用表 散列表TaggedPointerTaggedPointer对象是为了
2018-08-27
下一篇 
JavaScript的数组常用操作 JavaScript的数组常用操作
for in 遍历数组(不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象)var a = [1, 2, 3]; a.foo = true; for (var key in a) { console.log(key); } //
2018-07-30
  目录