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