【iOS 开发】多线程 GCD 的使用汇总

在日常的 iOS 开发中,关于多线程使用的较多的就是 Grand Central Dispatch(GCD) 了,GCD 会自动利用更多的 CPU 内核,会自动管理线程的生命周期,总之 GCD 的好处还是非常之多的,下面就对 GCD 的使用进行一个汇总。


GCD 的创建

GCD 中两个核心概念:队列任务 ,一般基本的用法只需要创建这两步即可。

1. 队列的创建

队列有两种方式:串行队列并发队列

  • 串行队列(Serial Dispatch Queue) :让任务一个接着一个地执行,一个任务执行完毕后,再执行下一个任务。
1
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
  • 并发队列(Concurrent Dispatch Queue) :可以让多个任务同时一起执行,但是并发队列只有在 异步(dispatch_async) 函数下才有效。
1
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);

2. 任务的创建

执行任务也有两种方式:同步执行异步执行

  • 同步执行(sync) :等待线程任务完成之后,才会进行接下来的操作。
1
2
3
4
dispatch_sync(queue, ^ {
NSLog(@"同步执行的任务");
});
  • 异步执行(async) :线程任务单独执行,不会影响接下来的操作。
1
2
3
4
dispatch_async(queue, ^ {
NSLog(@"异步执行的任务");
});

GCD 的使用

iOS 开发过程中,我们一般在 主线程 里边进行 UI 刷新,例如:点击、滚动、拖拽 等事件,而耗时的操作则会放在 子线程 里边进行。而当我们有时候在 子线程 完成了耗时操作时,需要回到主线程,那么就用到了下面这个方法。

1
2
3
4
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"回到主线程");
});

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
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 1 回到主线程");
});
});
dispatch_sync(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 2 回到主线程");
});
});
NSLog(@"----------- 结束 -----------");

因为 串行队列 是按顺序执行任务,而 同步任务 又是等待线程任务完成之后,才会进行接下来的操作,所以基本和不使用多线程的效果是相同的,下面是打印结果:

1
2
3
4
5
6
7
8
9
10
----------- 开始 -----------
任务 1
任务 1
任务 1
任务 2
任务 2
任务 2
----------- 结束 -----------
任务 1 回到主线程
任务 2 回到主线程

2. 串行队列 + 异步执行

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
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 1 回到主线程");
});
});
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 2 回到主线程");
});
});
NSLog(@"----------- 结束 -----------");

由于 异步任务 是单独执行,所以不会影响到正常代码的执行,所以明显就和上面有区别了,下面是打印结果:

1
2
3
4
5
6
7
8
9
10
----------- 开始 -----------
----------- 结束 -----------
任务 1
任务 1
任务 1
任务 2
任务 2
任务 2
任务 1 回到主线程
任务 2 回到主线程

3. 并发队列 + 异步执行

由于 并发队列同步任务 中是无效的,并发队列 + 同步执行串行队列 + 同步执行 没有任何区别,所以跳过直接看 并发队列 + 异步执行

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
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 1");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 1 回到主线程");
});
});
dispatch_async(queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 2");
}
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"任务 2 回到主线程");
});
});
NSLog(@"----------- 结束 -----------");

由于 并发队列 是多个任务同时执行的,所以可以看到 任务1任务2 基本是同时执行的,下面是打印结果:

1
2
3
4
5
6
7
8
9
10
----------- 开始 -----------
----------- 结束 -----------
任务 1
任务 2
任务 1
任务 2
任务 1
任务 2
任务 1 回到主线程
任务 2 回到主线程

4. notification 队列组(异步 + 并发)

上面 并发队列 + 异步执行 是各自任务完成后各自回到 主线程 。但是有时候我们会有这样的需求:分别异步执行两个耗时操作,然后当两个耗时操作都执行完毕后再回到 主线程 执行操作,这时候我们可以用到 GCDnotification 队列组。

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
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 1");
}
});
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 2");
}
});
/* 在组内的任务都运行完了之后再回到主线程 */
dispatch_group_notify(group, dispatch_get_main_queue(), ^ {
NSLog(@"回到主线程");
});
NSLog(@"----------- 结束 -----------");

notification 队列组会等到两个任务都执行完才会回到主线程,下面是打印结果:

1
2
3
4
5
6
7
8
9
----------- 开始 -----------
----------- 结束 -----------
任务 1
任务 2
任务 1
任务 2
任务 1
任务 2
回到主线程

5. wait 队列组(同步 + 并发)

上面说过 并发队列同步任务 中时无效的,但是 wait 队列组就能实现这个 同步 + 并发 效果。

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
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 1");
}
});
dispatch_group_async(group, queue, ^ {
for (int i=0; i<3; i++) {
NSLog(@"任务 2");
}
});
/* 等待组内的任务都完成后,继续运行 */
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
/* 回到主线程 */
dispatch_async(dispatch_get_main_queue(), ^ {
NSLog(@"回到主线程");
});
NSLog(@"----------- 结束 -----------");

可以看到任务是并发执行的,并且 结束 的打印在任务完成之后,下面是打印结果:

1
2
3
4
5
6
7
8
9
----------- 开始 -----------
任务 2
任务 1
任务 2
任务 1
任务 2
任务 1
----------- 结束 -----------
回到主线程

6. 延时执行的代码

当我们需要延迟执行一段代码时,就可以用到 GCDdispatch_after 方法。

1
2
3
4
5
6
7
8
NSLog(@"----------- 开始 -----------");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
NSLog(@"延迟 3 秒后执行的代码"); // 更改秒数只要更改上面的数字 3 即可
});
NSLog(@"----------- 结束 -----------");

这个方法是异步执行的,所以不会卡住界面,从时间可以看出延迟了 3 秒后才打印的,下面是打印结果:

1
2
3
2017-06-09 12:28:44.307 ----------- 开始 -----------
2017-06-09 12:28:44.307 ----------- 结束 -----------
2017-06-09 12:28:47.560 延迟 3 秒后执行的代码

7. 只执行一次的代码

使用下面方法执行的代码,在整个程序运行过程中,只会被执行一次,执行过一次以后就再也不会被执行,使用的是 GCDdispatch_once 方法。

1
2
3
4
5
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^ {
NSLog(@"只执行一次的代码");
});

8. 同时执行多次的代码

通常我们会用 for 循环来使代码执行多次,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply ,使我们可以将代码执行多次。

1
2
3
4
5
6
7
8
9
NSLog(@"----------- 开始 -----------");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"同时执行多次的代码 %zd",index); // 更改上面的数字 5 即可更改执行次数
});
NSLog(@"----------- 结束 -----------");

dispatch_applyfor 循环是有区别的,for 循环是按顺序依次执行多次,而 dispatch_apply 是同时执行多次,可以看到下面的 5 次几乎是同一时间执行的,下面是打印结果:

1
2
3
4
5
6
7
----------- 开始 -----------
同时执行多次的代码 0
同时执行多次的代码 2
同时执行多次的代码 3
同时执行多次的代码 1
同时执行多次的代码 4
----------- 结束 -----------

9. 定时器

一般情况下,都是使用 NSTimer 来实现定时器,但是 GCD 其实也是可以实现定时器功能的,并且 GCD 定时器不受 RunLoop 约束,比 NSTimer 更加准时。

1
2
3
4
5
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer; // 定时器对象
@end
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
NSLog(@"----------- 开始 -----------");
__block NSInteger time = 0;
/* 获得队列 */
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/* 创建定时器(dispatch_source_t 本质还是一个 OC 对象) */
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/* 设置定时器属性 */
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); // 1 秒后开始执行
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); // 每隔 1 秒执行一次
dispatch_source_set_timer(self.timer, start, interval, 0);
/* 回调 */
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"计时 %ld 秒", ++time);
if (time == 5) {
/* 取消定时器 */
dispatch_cancel(self.timer);
self.timer = nil;
}
});
/* 启动定时器 */
dispatch_resume(self.timer);
NSLog(@"----------- 结束 -----------");

下面是打印结果:

1
2
3
4
5
6
7
2017-06-09 14:55:37.894 ----------- 开始 -----------
2017-06-09 14:55:37.894 ----------- 结束 -----------
2017-06-09 14:55:38.896 计时 1 秒
2017-06-09 14:55:39.895 计时 2 秒
2017-06-09 14:55:40.895 计时 3 秒
2017-06-09 14:55:41.895 计时 4 秒
2017-06-09 14:55:42.895 计时 5 秒

用法大概就这么多,有疑问或者不对的地方可以在下面留言,有需要的可以收藏一下。

将来的你,一定会感激现在拼命的自己,愿自己与读者的开发之路无限美好。

我的传送门: 博客简书微博GitHub

如果觉得我的文章对您有用,请随意打赏吧,您的支持将鼓励我继续创作!
0%