0%

GCD概要

什么是GCD

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

在导入GCD之前,Cocoa 框架提供了 NSObject 类的 performSelectorInBackground:withObject 实例方法和 performSelectorOnMainThread 实例方法等简单的多线程编程技术。

dispatch_after 用法

在3秒后将指定的 Block 追加到 Main Dispatch Queue 中的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSLog(@"begin...");

/**
因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以在比如每隔1/60秒执行的 RunLoop 中,Block 最快在3秒后执行,最慢在3秒+1/60秒后执行,
并且在 Main Dispatch Queue 有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。虽然在有严格时间的要求下使用会出现问题,但在想大致延迟执行处
理时,该函数是非常有效的。
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds.");
});

NSLog(@"end...");

第一个参数是指定时间用的 dispatch_time_t 类型的值。该值使用 dispatch_time 函数或 dispatch_walltime 函数生成。

dispatch_time 函数能够获取从第一个参数 dispatch_time_t 类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是之前源代码中出现的DISPATCH_TIME_NOW。这表示现在的时间。即以下源代码可得到表示从现在开始1秒后的 dispatch_time_t 类型的值。

1
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

数值和 NSEC_PER_SEC 的乘积得到单位为毫微秒的数值。“ull”是C语言的数值字面量,是显示表明类型时使用的字符串(表示“unsigned long long”)。如果使用 NSEC_PER_MSEC 则可以以毫秒为单位计算。以下源代码获取表示从现在开始150毫秒后时间的值。TI

1
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);

dispatch_walltime 函数由 POSIX 中使用的 struct timespec 类型的时间得到 dispatch_time_t 类型的值。dispatch_time 函数通常用于计算时间,而 dispatch_walltime 函数用于计算绝对时间。例如在 dispatch_after 函数中想指定 2020年01月01日01时01分01秒这一绝对时间的情况,这可以作为粗略的闹钟功能使用。

struct timespec 类型的时间可以很轻松的通过 NSDate 类对象生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}

上面的源代码可由 NSDate 类对象获取能传递给 dispatch_after 函数的 dispatch_time_t 类型的值。

dispatch_group_t 用法

在追加到 Dispatch Queue 中的多个处理全部结束后想执行结束处理,这种情况会经常出现。

例如下面代码:追加3个 Block 到 Global Dispatch Queue,这些 Block 如果全部执行完毕,就会执行 Main Dispatch Queue 中结束处理用的 Block。

1
2
3
4
5
6
7
8
9
10
- (void)testDispatchGroupNotify {
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(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_async(group, queue, ^{NSLog(@"blk3");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
}

无论向什么样的 Dispatch Queue 中追加处理,使用 Dispatch Queue 都可以监视这些处理执行的结束。一旦检测到所有的处理执行结束,就可以将结束的处理追加到 Dispatch Queue 中。这就是使用

Dispatch Group 的原因。

在Dispatch Group中也可以使用 dispatch_group_wait 函数仅等待全部处理执行结束。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)testDispatchGroupWait1 {
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(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"done.");
}

dispatch_group_wait 函数的第二个参数指定为等待时间(超时)。它属于 dispatch_time_t 类型的值。该源代码使用 DISPATCH_DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group 的处理尚未结束,就会一直等待,中途不能取消。

如同 dispatch_after 函数说明的那样,指定等待间隔为1秒时应做如下处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)testDispatchGroupWait2 {
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
dispatch_group_t group = dispatch_group_create();
long result = dispatch_group_wait(group, time);
if (result == 0) {
/**
属于 Dispatch Group 的全部处理执行结束
*/
} else {
/**
属于 Dispatch Group 的某一个处理还在执行中
*/
}
}

如果 dispatch_group_wait 函数的返回值不为0,就意味着虽然经过了指定的时间,但属于 Dispatch Group 的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为 DISPATCH_TIME_FOREVER,由 dispatch_group_wait 函数返回时,由于属于 Dispatch Group 的处理必定全部执行结束,因此返回值恒为0。

这里的“等待”是什么意思呢?这意味着一旦调用 dispatch_group_wait 函数,该函数就处于调用的状态而不返回。即执行 dispatch_group_wait 函数的所在的线程停止。在经过 dispatch_group_wait 函数中指定的时间或属于指定 Dispatch Group 的处理全部执行结束之前,执行该函数的线程停止。

dispatch_barrier_async 用法

如果像下面这样简单地在 dispatch_async 函数中加入写入处理,那么根据 Concurrent Dispatch Queue 的性质,就有可能在写入处理前面的读取处理中读取到与期待不符的数据,还可能因非法访问导致应用程序异常结束。如果追加多个写入处理,则可能发生更多的问题,比如数据竞争等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)testReadAndWrite {
dispatch_queue_t queue = dispatch_queue_create("com.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{NSLog(@"blk0_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk1_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk2_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk3_for_reading");});

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

dispatch_async(queue, ^{NSLog(@"blk4_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk5_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk6_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk7_for_reading");});

}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
2020-02-01 10:33:21.908239+0800 GCD_Demo[1852:30606] blk0_for_reading
2020-02-01 10:33:21.908272+0800 GCD_Demo[1852:30354] blk1_for_reading
2020-02-01 10:33:21.908349+0800 GCD_Demo[1852:30705] blk2_for_reading
2020-02-01 10:33:21.908409+0800 GCD_Demo[1852:30706] blk3_for_reading
2020-02-01 10:33:21.908446+0800 GCD_Demo[1852:30606] blk4_for_reading
2020-02-01 10:33:21.908464+0800 GCD_Demo[1852:30354] blk5_for_reading
2020-02-01 10:33:21.908466+0800 GCD_Demo[1852:30707] blk_for_writing
2020-02-01 10:33:21.908490+0800 GCD_Demo[1852:30705] blk6_for_reading
2020-02-01 10:33:21.908498+0800 GCD_Demo[1852:30708] blk7_for_reading

因此我们要使用 dispatch_barrier_async 函数。dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并行执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中。然后在由 dispatch_barrier_async 函数追加的处理执行完毕后,Concurrent Dispatch Queue 才恢复为一般的动作,追加到该 Concurrent Dispatch Queue 的处理又开始并行执行。

使用 dispatch_barrier_async 的代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testReadAndWrite2 {
dispatch_queue_t queue = dispatch_queue_create("com.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{NSLog(@"blk0_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk1_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk2_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk3_for_reading");});

dispatch_barrier_async(queue, ^{NSLog(@"blk_for_writing");});

dispatch_async(queue, ^{NSLog(@"blk4_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk5_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk6_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk7_for_reading");});
}

程序运行的结果如下:

1
2
3
4
5
6
7
8
9
2020-02-01 10:52:59.739217+0800 GCD_Demo[2063:43192] blk0_for_reading
2020-02-01 10:52:59.739225+0800 GCD_Demo[2063:43191] blk1_for_reading
2020-02-01 10:52:59.739240+0800 GCD_Demo[2063:42655] blk2_for_reading
2020-02-01 10:52:59.739244+0800 GCD_Demo[2063:43193] blk3_for_reading
2020-02-01 10:52:59.739375+0800 GCD_Demo[2063:43193] blk_for_writing
2020-02-01 10:52:59.739480+0800 GCD_Demo[2063:42655] blk5_for_reading
2020-02-01 10:52:59.739483+0800 GCD_Demo[2063:43193] blk4_for_reading
2020-02-01 10:52:59.739503+0800 GCD_Demo[2063:43191] blk6_for_reading
2020-02-01 10:52:59.739510+0800 GCD_Demo[2063:43192] blk7_for_reading

使用 Concurrent Dispatch Queue 和 dispatch_barrier_async 函数可实现高效率的数据库访问和文件访问。

dispatch_barrier_async 函数的处理流程图:

dispatch_sync 用法

dispatch_async 函数,就是将指定的Block“非同步”地追加到指定的 Dispatch Queue 中,dispatch_async 函数不做任何等待。

dispatch_sync 函数,就是将指定的Block“同步”地追加到指定的 Dispatch Queue 中。在追加的Block结束之前,dispatch_sync 函数会一直等待。如 dispatch_group_wait 函数一样,“等待”意味着当前线程停止。

我们先假设这样一种情况,执行 Main Dispatch Queue 时,使用另外的线程 Global Dispatch Queue 进行处理,处理结束后立即使用所得到的结果。在这种情况下就要使用 dispatch_sync 函数。

1
2
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{/* 处理 */});

一旦调用 dispatch_sync 函数,那么在指定的处理执行结束之前,该函数不会返回。dispatch_sync 函数可简化源代码,也可说是简易版的 dispatch_group_wait 函数。正因为 dispatch_sync 函数使用简单,所以也容易引起问题,即死锁。

例如如果在主线程中执行以下源代码就会死锁。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)testDispatchSync1 {

NSLog(@"begin...");

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"Hello");
});

NSLog(@"end...");

}

该源代码在 Main Dispatch Queue 即主线程中执行指定的Block,并等待其执行结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到 Main Dispatch Queue 的Block。下面的例子也一样。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)testDispatchSync2 {

NSLog(@"begin...");

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

NSLog(@"end...");

}

Main Dispatch Queue 中执行的Block等待 Main Dispatch Queue 中要执行的Block 执行结束。这样的死锁就像在画像上画画一样。

当然 Serial Dispatch Queue 也会引起相同的问题。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)testDispatchSync3 {

NSLog(@"begin...");

dispatch_queue_t queue = dispatch_queue_create("com.gcd.mySerialDispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"Hello");});
});

NSLog(@"end...");

}

由 dispatch_barrier_async 函数中含有 async 可推测出,相应的也有 dispatch_barrier_sync 函数。dispatch_barrier_async 函数的作用是在等待追加的处理全部执行结束后,再追加处理到 Dispatch Queue 中,此外,它还与 dispatch_sync 函数相同,会等待追加处理的执行结束。

dispatch_apply 用法

dispatch_apply 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API。该函数按指定的次数将指定的Block追加到指定的 Dispatch Queue中,并等待全部处理执行结束。

1
2
3
4
5
6
7
- (void)testDispatchApply1 {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"blk %zu", index);
});
NSLog(@"done");
}

程序的输出结果:

1
2
3
4
5
6
7
8
9
10
11
2020-02-01 12:40:31.343067+0800 GCD_Demo[3361:105895] blk 1
2020-02-01 12:40:31.343067+0800 GCD_Demo[3361:105839] blk 2
2020-02-01 12:40:31.343074+0800 GCD_Demo[3361:105885] blk 0
2020-02-01 12:40:31.343078+0800 GCD_Demo[3361:105940] blk 4
2020-02-01 12:40:31.343079+0800 GCD_Demo[3361:105941] blk 7
2020-02-01 12:40:31.343078+0800 GCD_Demo[3361:105939] blk 3
2020-02-01 12:40:31.343085+0800 GCD_Demo[3361:105894] blk 5
2020-02-01 12:40:31.343085+0800 GCD_Demo[3361:105938] blk 6
2020-02-01 12:40:31.343224+0800 GCD_Demo[3361:105895] blk 8
2020-02-01 12:40:31.343235+0800 GCD_Demo[3361:105839] blk 9
2020-02-01 12:40:31.343787+0800 GCD_Demo[3361:105839] done

因为在 Global Dispatch Queue 中执行处理,所以各个处理的执行时间不定。但是输出结果中最后的 done 必定在最后的位置上。这是因为 dispatch_apply 函数会等待全部处理执行结束。

第一个参数为重复次数,第二个参数为追加对象的 Dispatch Queue,第三个参数为追加的处理。与到目前为止所出现的例子不同,第三个参数的Block为带有参数的Block。这是为了按第一个参数重复追加Block并区分各个Block而使用。例如要对 NSArray 类对象的所有元素执行处理时,不必一个一个编写 for 循环部分。

另外,由于 dispatch_apply 函数也与 dispatch_sync 函数相同,会等待处理执行结束,因此推荐在 dispatch_async 函数中非同步地执行 dispatch_apply 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (void)testDispatchApply2 {

NSArray *array = [NSArray arrayWithObjects:@"obj1", @"obj2", @"obj3", @"obj4", @"obj5", @"obj6", nil];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/** 在 Global Dispatch Queue 中非同步执行 */
dispatch_async(queue, ^{

/**
Global Dispatch Queue
等待 dispatch_apply 函数中全部处理执行结束
*/
dispatch_apply([array count], queue, ^(size_t index) {
/**
并列处理包含在 NSArray 对象的全部对象
*/
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});

/**
dispatch_apply 函数中的处理全部执行结束
*/

/**
在 Main Dispatch Queue 中非同步执行
*/
dispatch_async(dispatch_get_main_queue(), ^{
/**
在 Main Dispatch Queue 中执行处理
用户界面更新等
*/
NSLog(@"done");
});

});

}

程序执行结果:

1
2
3
4
5
6
7
2020-02-01 13:17:57.785290+0800 GCD_Demo[3821:122905] 4: obj5
2020-02-01 13:17:57.785285+0800 GCD_Demo[3821:122911] 5: obj6
2020-02-01 13:17:57.785283+0800 GCD_Demo[3821:122895] 3: obj4
2020-02-01 13:17:57.785283+0800 GCD_Demo[3821:122896] 0: obj1
2020-02-01 13:17:57.785283+0800 GCD_Demo[3821:122897] 2: obj3
2020-02-01 13:17:57.785283+0800 GCD_Demo[3821:122894] 1: obj2
2020-02-01 13:17:57.811750+0800 GCD_Demo[3821:122848] done

dispatch_suspend / dispatch_resume 用法

当追加大量处理到 Dispatch Queue 时,在追加处理的过程中,有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。在这种情况下,只要挂起 Dispatch Queue 即可。当可以执行时再恢复。

dispatch_suspend 函数挂起指定的 Dispatch Queue。

1
dispatch_suspend(queue);

Dispatch_resume 函数恢复指定的 Dispatch Queue。

1
dispatch_resume(queue);

这些函数对已经执行的处理没有影响。挂起后,追加到 Dispatch Queue 中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

Dispatch Semaphore 用法

如前所述,当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用 Serial Dispatch Queue 和 dispatch_barrier_async 函数可避免这类问题,但有必要进行更细粒度的排他控制。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)testDispatchSemaphore1 {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i=0; i < 10000; ++i) {
dispatch_async(queue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}

}

因为该源代码使用 Global Dispatch Queue 更新 NSMutableArray 类对象,所以执行后由内存错误导致应用程序异常结束的概率很高。此时应使用 Dispatch Semephore。

Dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。而在Dispatch Semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。

1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

参数表示计数的初始值。本例将计数值初始化为“1”。

1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait 函数等待 Dispatch Semaphore 的计数值达到大于或等于 1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从 dispatch_semaphore_wait 函数返回。第二个参数与 dispatch_group_wait 函数等相同,由 dispatch_time_t 类型值指定等待时间。该例的参数意味着永久等待。另外,dispatch_semaphore_wait 函数的返回值也与 dispatch_group_wait 函数相同。可像以下源代码这样通过返回值进行分支处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

long result = dispatch_semaphore_wait(semaphore, time);

if (result == 0) {
/**
由于 Dispatch Semaphore 的计数值达到大于等于 1
或者在待机中指定的时间内
Dispatch Semaphore 的计数值达到大于等于 1
所以 Dispatch Semaphore 的计数值减去 1。

可执行需要进行排他控制的处理
*/
} else {
/**
由于 Dispatch Semaphore 的计数值为0
因此在达到指定时间为止待机
*/
}

dispatch_semaphore_wait 函数返回0时,可安全地执行需要进行排他控制的处理。该处理结束时通过 dispatch_semaphore_signal 函数将 Dispatch Semaphore 的计数值加1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- (void)testDispatchSemaphore2 {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/**
生成 Dispatch Semaphore
Dispatch Semaphore 的计数初始值设定为 “1”。
保证可访问 NSMutableArray 类对象的线程同时只能有1个。
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 100000; i++) {
dispatch_async(queue, ^{
/**
等待 Dispatch Semaphore
一直等待,直到 Dispatch Semaphore 的计数值达到大于等于1。
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

/**
由于Dispatch Semaphore 的计数值达到大于等于1
所以将 Dispatch Semaphore 的计数值减去 1,
dispatch_semaphore_wait 函数执行返回。

即执行到此时的 Dispatch Semaphore 的计数值恒为 0。

由于可访问 NSmutableArray 类对象的线程只有1个,因此可以安全的进行更新。
*/
[array addObject:[NSNumber numberWithInt:i]];

/**
排他控制处理结束,所以通过 dispatch_semaphore_signal 函数
将 Dispatch Semaphore 的计数值加1。
如果有通过 dispatch_semaphore_wait 函数
等待 Dispatch Semaphore 的计数值增加的线程,
就由最先等待的线程执行。
*/
dispatch_semaphore_signal(semaphore);
});
}

}

dispatch_once 用法

dispatch_once 函数是保证在应用程序中只执行一次指定处理的API。如果使用 dispatch_once 函数,源代码为:

1
2
3
4
5
6
7
8
9
10
11
- (void)testDispatchOnce {

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/**
初始化
*/
NSLog(@"当前初始化代码只会执行一次");
});

}

通过 dispatch_once 函数,该源代码即使在多线程环境下执行,也可保证百分之百安全。在生成单例对象时使用。

Dispatch I/O 用法

Dispatch I/O 和 Dispatch Data,可以做到一次使用多个线程更快地并列读取。dispatch_io_create 函数生成 Dispatch I/O,并指定发生错误时用来执行处理的Block,以及执行该Block的 Dispatch Queue。如果想提高文件读取速度,可以尝试使用 Dispatch I/O。

Dispatch Source 用法

GCD中除了主要的 Dispatch Queue 外,还有不太引人注目的 Dispatch Source。下面展示一个使用 DISPATCH_SOURCE_TYPE_TIMER 的定时器的例子。在网络编程的通信超时等情况下可使用该例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void) testDispatchSource {

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

/**
将定时器设定为 15 秒后。
不指定为重复。
允许延迟 1 秒。
*/
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 15ull * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull * NSEC_PER_SEC);

/** 指定定时器指定时间内执行的处理 */
dispatch_source_set_event_handler(timer, ^{
NSLog(@"wakeup!");

/** 取消 Dispatch Source 时的处理 */
dispatch_source_cancel(timer);
});

/** 指定取消 Dispatch Source 时的处理 */
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"canceled");

});

/** 启动 Dispatch Source */
dispatch_resume(timer);

}

上面的代码在15秒之后会执行“wakeup!”打印,打印完就取消当前的定时器。实际上 Dispatch Queue 没有“取消”这一概念。一旦将处理追加到 Dispatch Queue 中,就没有方法将该处理去除,也没有方法可在执行中取消处理。编程人员要么在处理中导入取消这一概念,要么放弃取消,或者使用 NSOperationQueue 等其他方法。

Dispatch Source 与 Dispatch Queue 不同,是可以取消的。而且取消时必须执行的处理可以指定为回调的Block形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)testDownloadImg {

NSString *url = @"http://images.apple.com/jp/iphone/features/includes/camera-gallery/03-20100607.jpg";

[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

if (!error) {

/**
在 Global Dispatch Queue 中对下载的数据进行解析处理,
不妨碍主线程可长时间处理。
*/

dispatch_async(dispatch_get_main_queue(), ^{
/**
在 Main Dispatch Queue 中使用解析结果,
对用户界面尽心刷新处理。
*/
});

} else {
/** 发生错误 */
NSLog(@"error:%@", error);
}

});

}];

}

为了不妨碍主线程的运行,在另外的线程中解析下载的数据。实现数据解析的就是通过 dispatch_get_global_queue 函数得到的一般优先级的 Global Dispatch Queue 和在 Global Dispatch Queue 中执行解析处理的 dispatch_async 函数。解析处理后为了反映到用户界面,需要在主线程中进行用户界面的更新。通过 dispatch_get_main_queue 函数得到的 Main Dispatch Queue 和在 Main Dispatch Queue 中使该处理执行的 dispatch_async 函数实现了此处理。

那么为了不妨碍主线程的运行,网络下载处理也是使用GCD的线程更好吗?答案是否定的。网络编程强烈推荐使用异步 API。如果在网络编程中使用线程,就很可能会产生大量使用线程的倾向,会引发很多问题。例如每个连接都使用线程,很快就会用尽线程栈内存等。因为Cocoa框架提供了用于异步网络通信的API,所以在网络编程中不可使用线程。务必使用用于异步网络通信的API。

参考链接

《iOS与OS X多线程和内存管理》