在移动应用开发中,实现良好的性能和响应性对于用户体验至关重要。而 Grand Central Dispatch (GCD) 作为 iOS 平台上的多线程编程技术,为开发者提供了一种简单而强大的方式来实现异步任务的执行,优化应用程序的性能。本篇文章将深入探讨 GCD 的各项功能和用法,帮助读者更好地理解和应用这一强大工具。
在学习 iOS 开发过程中,我们经常会遇到各种关于多线程编程的问题。而对于初学者来说,理解和掌握多线程编程可能是一项具有挑战性的任务。在此之中,Grand Central Dispatch(GCD)作为一种强大的技术,能够帮助开发者简化多线程编程的复杂性,提升应用的性能和响应速度。
Grand Central Dispatch(GCD)是一种用于实现异步执行任务的技术,主要用于优化应用程序以支持多核处理器和其他对称多处理系统。通过 GCD,开发者只需定义想执行的任务并将其追加到适当的 dispatch Queue 中,GCD 就能自动生成必要的线程并计划执行任务,从而提高了应用的效率。
在学习和应用 GCD 之前,我们需要了解几个核心概念:任务(Task)、队列(Queue)、以及串行队列和并发队列的区别。
任务是指在线程中执行的操作,通常以 block 的形式存在。而队列则是用来存放任务的等待队列,采用先进先出(FIFO)的原则执行任务。GCD 中有两种方式执行任务:同步执行和异步执行。
Dispatch Queue 是执行任务的等待队列,通过 dispatch_async 函数将任务追加到指定的 Dispatch Queue 中,即可使任务在其他线程执行。
dispatch_async(queue, ^{
// 想执行的任务
});
Dispatch Queue 包括串行队列和并发队列两种类型,根据需要选择不同的队列类型。
dispatch_queue_create 方法用于创建串行队列和并发队列。
// 创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
// 创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 获取全局并发队列(不同优先级)
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue 函数用于变更队列的优先级或执行阶层。
dispatch_set_target_queue(queue1, queue2);
通过设置目标队列,可以实现任务的优先级调整或者阻止任务并发执行。
dispatch_after 函数用于延迟一定时间后执行任务。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 延迟执行的任务
});
Dispatch Group 是一种用于监视多个任务是否全部执行完毕的机制,常用于等待多个异步任务执行结束后再执行后续操作。
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 异步任务1
});
dispatch_group_async(group, queue, ^{
// 异步任务2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 所有任务执行完成后的处理
});
在访问数据库或文件等资源时,Serial Queue可以有效避免数据竞争。然而,有时我们需要异步执行两组操作,并且第一组操作执行完毕后才能开始执行第二组操作。这时,我们就需要类似于栅栏一样的方法将这两组异步执行的操作分隔开来,确保它们的执行顺序。GCD提供了栅栏方法 dispatch_barrier_async
来解决这个问题。它多用于异步操作。
这个函数通常与创建的并发队列(Concurrent Dispatch Queue)一起使用。下面是一个示例:
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
这段代码会先执行任务1和任务2,然后等待它们执行完毕后再执行栅栏任务,最后执行任务3和任务4。
GCD提供了同步执行任务的创建方法 dispatch_sync
和异步执行任务的创建方法 dispatch_async
。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
dispatch_async
是非同步的,即将指定的block非同步地追加到队列中,不会进行等待。
dispatch_sync
是同步的,即将指定的block同步地追加到队列中,会等待任务执行完毕后再返回。
然而,使用 dispatch_sync
时需要注意避免死锁。例如:
- (void)syncLockForever {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue,^ {
NSLog(@"hello");
});
}
上述代码在主线程中执行,但是同步地向主队列中追加任务,导致了死锁,因为主队列在等待当前任务完成,而当前任务又在等待主队列。这种情况会导致无限等待。
dispatch_apply
函数可以按照指定的次数将指定的block追加到指定的队列中,并等待全部执行结束。它的作用类似于for循环。
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
以上代码会在全局默认并发队列中执行一个包含6次迭代的任务,打印出每次迭代的索引以及当前线程。
dispatch_suspend
和 dispatch_resume
是两个可以随时挂起和恢复队列的函数。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_suspend(queue); // 挂起队列
dispatch_resume(queue); // 恢复队列
在实际开发中,dispatch_semaphore
主要用于以下两个方面:
在下面的示例中,我们展示了如何使用信号量来实现线程同步:
- (void)semaphoreSync {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore); // 发送信号
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号
NSLog(@"semaphore---end, number = %d", number);
}
在上述示例中,我们创建了一个全局并发队列,并在队列中异步执行了一个任务。在任务中,我们模拟了一个耗时操作,并在完成后发送了一个信号。在主线程中,我们通过 dispatch_semaphore_wait
函数等待这个信号,确保任务执行完成后再继续执行后续代码。
dispatch_once
函数通常用于创建单例对象,确保该对象在程序生命周期内只被初始化一次。下面是一个简单的示例:
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static id sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
在上述示例中,我们通过 dispatch_once
函数保证了 sharedInstance
方法中的初始化代码只会被执行一次。这样,无论多少个线程同时调用 sharedInstance
方法,都能够保证只返回同一个单例对象,而不会创建多个实例。
通过合理地使用 dispatch_once
函数,我们能够简洁地实现单例模式,并且保证线程安全性。