GCD是苹果公司推出的专门用于简化多线程编程的技术。在GCD中,程序员已经不再需要去关心有关线程的操作(如:线程创建、线程销毁、线程调度),而是引入了任务和队列两个核心概念。
在使用GCD处理多任务执行时,只要按照如下步骤执行即可。
在Block中定义需要执行的任务内容。
把任务添加到队列queue中。
GCD对队列中的任务,按照“先进先出”的原则,根据任务添加到队列的顺序来对队列进行处理,GCD会根据任务和队列的类型,自动在多个线程之间分配工作。
在GCD中,需要处理的事务统一使用Block封装起来,称为任务。任务有两种类型,同步任务和异步任务。通过调用不同的函数,来设置任务的类型。同时,任务编写在函数的Block参数中。
// 异步任务:执行任务时,会在另外的线程中执行,即可能会创建新的线程。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 同步任务:执行任务时,会在当前的线程中执行,当前线程有可能是主线程,也有可能是子线程。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
GCD中,队列是一个重要概念。系统提供了若干预定义的队列,其中包括可以获取应用程序的主队列(任务始终在主线程上执行,与更新UI相关的操作必须在主队列中完成)。另外,可以自由创建不同类型的队列,例如:并行队列和串行队列,队列的类型决定了任务的执行方式。GCD队列严格按照“先进先出”的原则,添加到GCD队列中的任务,始终会按照加入队列的顺序被执行。
并行队列:并行队列中的任务可以在多个线程之间分配执行,分配的原则由GCD控制,因此,并行队列中的任务,虽然执行时按照先进先出进行分配的,但由于各个任务被分配到不同的线程执行,因此其完成时间有可能不同,即:后分配的任务有可能先执行完成;并发队列一定需要和异步执行的任务(使用dispatch_async())结合起来使用才有意义。
串行队列:串行队列中的任务是按照顺序一个一个完成的,当一个任务完成后,才去执行下一个任务;因此,串行队列对应一个线程执行。
主队列:主队列也是一个串行队列,主队列中的任务都在主线程中执行。
// 获取系统定义的并行队列。一般来说,开发中通常不需要自己创建并行队列,使用系统提供的即可。
dispatch_get_global_queue(long identifier, unsigned long flags);
// 创建队列。可以创建并行队列,也可以创建串行队列。该方法常用于创建串行队列。
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
// 获取主队列
dispatch_get_main_queue(void);
在GCD中存在队列和任务两个核心概念,同时队列又分为并行队列、串行队列和主队列,任务包括异步任务和同步任务。GCD的使用方法就是把任务放到队列中执行,因而根据不同的任务类型和队列类型,就会存在6种组合。
1.异步任务+并行队列
// 把异步任务放到并行队列进行执行,异步任务会在不同的线程中执行,这是最常使用的一种组合。
-(void)viewDidLoad {
[super viewDidLoad];
// 获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建异步任务,并放到并行队列中执行
dispatch_async(queue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2-----", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3-----", [NSThread currentThread]);
});
}
2.异步任务+串行队列
// 对于异步任务放在串行队列中执行时,任务只会在一个新开的线程中,按照顺序进行执行。
-(void)viewDidLoad {
[super viewDidLoad];
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("new_queue", NULL);
// 创建异步任务,并放到串行队列中执行
dispatch_async(queue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
// ...
}
3.异步任务+主队列
// 把异步任务放在主队列中执行,由于主队列是一个特殊的串行队列,因此任务是串行执行的,但由于主队列对应序号为1的线程,因此,即便是异步任务,也不会再创建新的线程。
-(void)viewDidLoad {
[super viewDidLoad];
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 创建异步任务,并放到主队列中执行
dispatch_async(mainQueue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
// ...
}
4.同步任务+并行队列
同步任务的执行是在当前线程中完成的,因此,即便是把同步任务放在并行队列中执行,由于只有1个线程,任务也是一个一个按顺序执行(串行执行)的。
-(void)viewDidLoad {
[super viewDidLoad];
// 获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PTIORITY_DEFAULT, 0);
// 创建同步任务,并放到并行队列中执行
dispatch_sync(mainQueue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
// ...
}
5.同步任务+串行队列
同步任务放在串行队列中执行,任务会在当前线程依次执行。
-(void)viewDidLoad {
[super viewDidLoad];
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("new_queue", NULL);
// 创建同步任务,并放到串行队列中执行
dispatch_sync(queue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
// ...
}
6.同步任务+主队列
这种情况下,主线程会被阻塞,程序会挂死,不能使用。
-(void)viewDidLoad {
[super viewDidLoad];
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 同步任务,并放到主队列中执行
dispatch_sync(mainQueue, ^{
for(int 1 = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1-----", [NSThread currentThread]);
});
// ...
}
3 线程间通信
在涉及网络数据获取的过程中,一般会使用异步任务+并发队列进行数据获取。在并行队列中的任务还可以嵌套子任务,嵌套的子任务可以设置为在主线程中执行的任务。
// 示例
-(IBAction)downLoadImageView:(id)sender {
// 获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"task2-----", [NSThread currentThread]);
// 异步任务
dispatch_async(queue, ^{
// ...todo
// 返回主线程 执行...
dispatch_async(dispatch_get_main_queue(), ^{
});
});
}
4 队列组dispatch group
在串行队列中,任务是按照进入队列的顺序依次执行,因此任务和任务之间是有明确的先后顺序的。但是对于并行队列的任务来说,由于任务会被自动分配到不同的线程中执行,因此任务完成的顺序是不确定的。假如希望给并行队列中的任务设置执行顺序,例如,当任务A和任务B完成后,再去完成任务C,就需要使用到任务组dispatch group。
// 创建队列组
dispatch_group_t dispatch_group_create(void);
// 向队列组中插入一个异步任务。
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// 队列组中其他任务执行完成后,执行的任务,通常可以用来设置UI界面。
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// 示例
-(IBAction)startTask:(id)sender {
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"task2-----", [NSThread currentThread]);
// 创建队列组中的第一个异步任务
dispatch_group_async(group, queue, ^{
// ...
// 返回主线程 执行...
dispatch_async(dispatch_get_main_queue(), ^{
});
});
// 创建队列组中的第二个异步任务
dispatch_group_async(group, queue, ^{
// ...
// 返回主线程 执行...
dispatch_async(dispatch_get_main_queue(), ^{
});
});
// 任务组中的任务完成后,执行的动作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// ...
})
}
5 延迟执行操作
有时会希望把一些操作封装起来延迟一段时间后再执行。iOS开发中,有两种常用的方法可以实现延迟执行:一种是使用GCD;另外一种是使用NSRunLoop类中提供的方法。
// 在GCD中可以使用dispatch_after()函数,封装一段代码到Block中,在设置的延迟时间dispatch_time_t之后执行。
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
// 在NSRunLoop类中,也提供了有关延迟执行的方法。由于这些方法是对NSObject类的扩展,因此,所有的类都可以使用。
@interface NSObject (NSDelayedForming)
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inMode:(NSArray *)modes;
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
@end