GCD定时器
IOS开发中常用的定时器之一,以纳秒作为时间间隔单位,且运行不受RunLoop的运行模式的影响,相比NStimer定时器运行更为精准,Xcode自带的代码块,直接dispatch就可联想出来,传入参数即可,需要注意其本质为结构体,需要定义一个强指针(strong)修饰的对象引用它,否则它就会被销毁。
强引用声明dispatch_source_t对象
@property (nonatomic, strong) dispatch_source_t timer;
定时器代码块
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = dispatch_get_main_queue();
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
//执行的函数或代码块
[weakSelf countDownNotification];
});
dispatch_resume(_timer);
关闭定时器代码块
-(void)dealloc{
if (self.timer) {
dispatch_cancel(self.timer);
self.timer = nil;
LCCKLog(@"timer has been destoryed!");
}
}
定时器代码块中的代码作用
1.创建队列函数
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}
dispatch_get_main_queue() 函数调度获取主队列,返回绑定到主线程的默认队列,当用于非UI应用程序的进程时,它可能会产生不必要的副作用,对于这样的进程,应该避免使用主队列。
例如:
dispatch_queue_t queue = dispatch_get_main_queue();
2.创建GCD中的定时器
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t _Nullable queue) 函数创建一个新的分派源来监视底层系统对象,并自动地向分派队列提交一个处理程序块以响应事件。分派源不可重入。当分派源被挂起或事件处理程序块当前正在执行时接收的任何事件,将在恢复分派源或事件处理程序块返回后合并并交付。分派源是在非活动状态下创建的。创建源并设置任何所需属性(即处理程序、上下文等)之后,必须调用 dispatch_activate() ,以便开始事件交付。
参数 dispatch_source_type_t type : 声明分派源的类型。必须是已定义的dispatch_source_type_t常量之一。
参数: uintptr_t handle : 要监视的底层系统句柄。这个参数的解释由类型参数中提供的常量决定。
参数 unsigned long mask : 指定需要哪些事件的标志掩码。这个参数的解释由类型参数中提供的常量决定。
参数 dispatch_queue_t _Nullable queue : 事件处理程序块将提交到的分派队列。如果队列是DISPATCH_TARGET_QUEUE_DEFAULT,那么源将把事件处理程序块提交给默认的优先级全局队列。
例如:
/*
第一个参数:创建source的类型 DISPATCH_SOURCE_TYPE_TIMER:定时器
第二个参数:0
第三个参数:0
第四个参数:队列
*/
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
3.设置定时器
dispatch_source_set_timer(dispatch_source_t source,
dispatch_time_t start,
uint64_t interval,
uint64_t leeway);
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway) 函数设置计时器源的开始时间、间隔和回旋余地值(允许的误差值,0表示尽量精准)。为了提高系统的功耗和性能,定时器的任何触发都可能被系统延迟。允许延迟的上限可以配置为允许的误差值,下限由系统控制。如果指定的计时器源是使用DISPATCH_TIMER_STRICT掩码创建的,那么系统将尽最大努力严格遵守所提供的“leeway”值,即使它小于当前的下限。请注意,即使指定了此标志,也需要最少的延迟。“start”参数还决定计时器将使用哪个时钟:如果“start”是DISPATCH_TIME_NOW或使用dispatch_time(3)创建的,则计时器基于运行时间(在苹果平台上从mach_absolute_time()获得)。如果“start”是使用dispatch_walltime(3)创建的,那么计时器将基于gettimeofday(3)。如果计时器源已被取消,则调用此函数无效。
参数 dispatch_source_t source:定时器源对象。
参数 dispatch_time_t start : 计时器的开始时间。例如计时器在创建后第一次执行任务是在创建5秒后,参数则传入dispatch_walltime(NULL, 5.0 * NSEC_PER_SEC)。
参数 uint64_t interval : 计时器的纳秒间隔。对于一次性计时器,使用DISPATCH_TIME_FOREVER。
参数 uint64_t leeway : 计时器允许的误差时间,纳秒。
例如:
/*
第一个参数:定时器对象
第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时
第三个参数:间隔时间 GCD里面的时间 纳秒
第四个参数:误差值(表示允许的误差,0表示尽量精准)
*/
// * NSEC_PER_SEC 转换为秒
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
4.设置事件
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t _Nullable handler) 函数为给定的分派源设置事件处理程序块。
参数 dispatch_source_t source : 要修改的分派源。在此参数中传递NULL的结果是未定义的。
参数 dispatch_block_t _Nullable handler : 要提交到源的目标队列的事件处理程序块。
例如:
dispatch_source_set_event_handler(_timer, ^{
[weakSelf countDownNotification];
});
5.开始执行
dispatch_resume(dispatch_object_t object);
dispatch_resume(dispatch_object_t object) 函数恢复对分派对象上的块的调用。可以使用dispatch_suspend() 挂起分派对象,这会增加内部挂起计数。dispatch_resume() 是相反的操作,它使用暂停计数。当使用最后一个暂停计数时,将再次调用与对象关联的块。
出于向后兼容性的原因,对于非活动的、而不是挂起的分派源对象,dispatch_resume() 具有与调用dispatch_activate() 相同的效果。对于新代码,最好使用 dispatch_activate()。
如果指定的对象的挂起计数为零且不是非活动源,则此函数将导致断言并终止进程。
参数 dispatch_object_t object : 要恢复的对象。在此参数中传递NULL的结果是未定义的。
例如:
//开始执行
dispatch_resume(_timer);
6.关闭定时器
dispatch_cancel(object)
dispatch_cancel 函数取消指定的对象。类型泛型宏,根据第一个参数的类型映射到dispatch_block_cancel 或 dispatch_source_cancel。此函数对于任何其他对象类型都不可用。
参数 object :要取消的对象。在此参数中传递NULL的结果是未定义的。
例如:
dispatch_cancel(self.timer);
GCD计时器实现的简单倒计时按钮
#import <UIKit/UIKit.h>
@interface CountdownButton : UIButton
- (void)startCountdownCompletionhander:(void (^)(void))completionHanlder;
@end
#import "CountdownButton.h"
@interface CountdownButton()
@property (strong, nonatomic) dispatch_source_t timer;//强引用声明dispatch_source_t对象,否则它就会被销毁。
@end
@implementation CountdownButton
- (void)dealloc {
//定时器失效
[self timerInvalidate];
}
///类方法初始化按钮
+ (instancetype)buttonWithType:(UIButtonType)buttonType {
CountdownButton *button = [super buttonWithType:buttonType];
//按钮背景色
button.backgroundColor = HEXCOLOR(0xF56456);
//按钮标题字体大小
button.titleLabel.font = [UIFont systemFontOfSize:13];
//按钮圆角
button.layer.cornerRadius = 3.0;
//按钮超出层边界裁剪
button.layer.masksToBounds = YES;
//可以减小按钮标题字体大小以适应
button.titleLabel.adjustsFontSizeToFitWidth = YES;
//可以减小的最小比例
button.titleLabel.minimumScaleFactor = 0.8;
//设置按钮标题文本
[button setTitle:@"获取验证码" forState:UIControlStateNormal];
//设置按钮标题文本颜色
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
return button;
}
///初始化按钮
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//按钮背景色
self.backgroundColor = HEXCOLOR(0xF56456);
//按钮标题字体大小
self.titleLabel.font = [UIFont systemFontOfSize:13];
//按钮圆角
self.layer.cornerRadius = 3.0;
//按钮超出层边界裁剪
self.layer.masksToBounds = YES;
//可以减小按钮标题字体大小以适应
self.titleLabel.adjustsFontSizeToFitWidth = YES;
//可以减小的最小比例
self.titleLabel.minimumScaleFactor = 0.8;
//设置按钮标题文本
[self setTitle:@"获取验证码" forState:UIControlStateNormal];
//设置按钮标题文本颜色
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
return self;
}
///开始倒计时并在完成时处理程序
- (void)startCountdownCompletionhander:(void (^)(void))completionHanlder {
//倒计时时间(秒)
__block NSTimeInterval time = 5.0;
//如果self.timer为null,则进行创建
if (self.timer == NULL) {
//创建GCD中的定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
//设置定时器
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//设置事件
dispatch_source_set_event_handler(self.timer, ^{
//按钮是否启用
BOOL enabled;
//背景色
UIColor *backgroundColor;
//标题
NSString *title;
//标题颜色
UIColor *titleColor;
if (time <= 0) {
//倒计时结束
//定时器失效
[self timerInvalidate];
//设置按钮响应属性
enabled = YES;
backgroundColor = HEXCOLOR(0xF56456);
title = @"获取验证码";
titleColor = [UIColor whiteColor];
} else {
enabled = NO;
backgroundColor = HEXCOLOR(0xEEEEEE);
title = [NSString stringWithFormat:@"%2.0f秒后再次发送", time];
titleColor = HEXCOLOR(0xBDBDBD);
}
time -= 1.0;
//获取主队列,修改按钮样式
dispatch_async(dispatch_get_main_queue(), ^{
//将响应属性设置到按钮
self.enabled = enabled;
self.backgroundColor = backgroundColor;
[self setTitle:title forState:UIControlStateNormal];
[self setTitleColor:titleColor forState:UIControlStateNormal];
//判断是否执行倒计时完成块
if (self.isEnabled &&
completionHanlder) {
completionHanlder();
}
});
});
//开始执行计时器
dispatch_resume(self.timer);
}
}
///定时器失效
- (void)timerInvalidate {
if (self.timer) {
dispatch_cancel(self.timer);
}
self.timer = NULL;
}
@end
调用代码:
#import "CountdownButton.h"
@interface TestCodeController ()
@property (nonatomic, strong) CountdownButton *countdownButton;
@end
@implementation TestCodeController
- (void)viewDidLoad {
[super viewDidLoad];
///倒计时按钮
self.countdownButton = [[CountdownButton alloc]initWithFrame:CGRectZero];
[self.countdownButton addTarget:self action:@selector(againCountdown) forControlEvents:UIControlEventTouchUpInside];
[self.countdownButton startCountdownCompletionhander:^{
[self showAlert];
}];
[self.view addSubview:self.countdownButton];
[self.countdownButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(90, 35));
}];
}
///再次倒计时
- (void)againCountdown{
[self.countdownButton startCountdownCompletionhander:^{
[self showAlert];
}];
}
///显示提示
- (void)showAlert{
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"倒计时结束" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *findAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
}];
[alertView addAction:findAction];
[self presentViewController:alertView animated:YES completion:nil];
}
@end
样式如下:
Jietu20201117-224536.gif
GCD延迟执行block(块)中代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"要执行的代码");
});
dispatch_semaphore_t调度信号量对象
dispatch_semaphore_create(long value);
函数描述 : 使用初始值创建新的计数信号量。当两个线程需要协调特定事件的完成时,为该值传递0是很有用的。传递大于零的值对于管理有限的资源池非常有用,其中池的大小等于该值。调用dispatch_semaphore_signal 必须与调用wait()保持平衡。试图以低于值的计数来处理信号量会导致EXC_BAD_INSTRUCTION异常。
参数 :
value : 信号量的初始值。不要传递小于零的值。
返回值 : 新创建的信号量。
dispatch_semaphore_create(long value);
例如使用初始值创建新的计数信号量:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
函数描述 : 信号(增量)一个信号量。增加计数信号量。如果之前的值小于0,则此函数将唤醒当前在dispatch_semaphore_wait中等待的线程。
参数 :
dsema : 计数信号量。此参数不能为空。
返回值 : 如果之前的值小于零,则此函数将唤醒当前正在等待的进程。
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
函数描述 : 等待(衰减)信号量。减少计数信号量。如果结果值小于零,则此函数将等待信号出现后返回。
参数 :
dsema : 信号量。此参数不能为空。
timeout : 何时超时。常量DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER可以方便地使用。
返回值 : 如果成功,则返回零;如果超时,则返回非零。
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
注:
#define DISPATCH_TIME_NOW (0ull) //分派时间表示“现在”。
#define DISPATCH_TIME_FOREVER (~0ull) //分派时间表示“无限”。
一个信号量操作及其简单的小例子:
- (void)viewDidLoad {
[super viewDidLoad];
//使用初始值创建新的计数信号量。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//开辟子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"我输出后线程就被阻塞了");
//等待(减少)信号量。阻塞线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"我需等待3秒才能输出");
});
//延迟3秒执行函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//信号量(递增),唤醒线程
dispatch_semaphore_signal(semaphore);
});
}
输出如下 :
截屏2021-01-11下午11.52.23.png
dispatch_group_t 调度组(笔记均摘抄自NeroXie的深入理解GCD之dispatch_group链接:https://www.jianshu.com/p/e93fd15d93d3)
dispatch_group 调度组是监视一组块的机制。应用程序可以根据需要同步或异步监视组中的块。通过扩展,组可以用于同步依赖于其他任务完成情况的代码。请注意,组中的块可以在不同的队列上运行,每个块可以向组中添加更多块。调度组跟踪有多少块未完成,GCD保留该组,直到其所有相关块完成执行。
dispatch_group 的实现是基于dispatch_semaphore的。在看源码之前,我们先看一下我们是如何应用的。假设有这么场景:有一个A耗时操作,B和C两个网络请求操作,当ABC都执行完成后,刷新页面。我们可以用dispatch_group 实现。代码如下:
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block NSInteger number = 0;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);
number += 1;
NSLog(@"我耗时操作执行了");
});
//任务B
dispatch_group_enter(group);
[self sendTaskTwoRequestWithCompletion:^(id response) {
number += [response integerValue];
NSLog(@"我任务B执行了");
dispatch_group_leave(group);
}];
//任务C
dispatch_group_enter(group);
[self sendTaskThreeRequestWithCompletion:^(id response) {
number += [response integerValue];
NSLog(@"我任务C执行了");
dispatch_group_leave(group);
}];
//所有任务都执行了
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务执行完毕,任务结果 ----- %zd", number);
});
}
- (void)sendTaskTwoRequestWithCompletion:(void (^)(id response))completion {
//模拟一个网络请求
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(@2);
});
});
}
- (void)sendTaskThreeRequestWithCompletion:(void (^)(id response))completion {
//模拟一个网络请求
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
sleep(1);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(@3);
});
});
}
@end
打印结果如下:
2021-11-09 08:39:13.714426+0800 TestProduct[23932:898935] 我任务C执行了
2021-11-09 08:39:14.714474+0800 TestProduct[23932:898935] 我任务B执行了
2021-11-09 08:39:15.714368+0800 TestProduct[23932:899324] 我耗时操作执行了
2021-11-09 08:39:15.714741+0800 TestProduct[23932:898935] 任务执行完毕,任务结果 ----- 6
接下来我们根据上面的流程来看一下dispatch_group的相关API
dispatch_group_create
dispatch_group_t
dispatch_group_create(void)
{
return (dispatch_group_t)dispatch_semaphore_create(LONG_MAX);
}
dispatch_group_async 只是 dispatch_group_async_f 的封装
dispatch_group_async_f
void
dispatch_group_async_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt,
dispatch_function_t func)
{
dispatch_continuation_t dc;
_dispatch_retain(dg);
dispatch_group_enter(dg);
dc = fastpath(_dispatch_continuation_alloc_cacheonly());
if (!dc) {
dc = _dispatch_continuation_alloc_from_heap();
}
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_GROUP_BIT);
dc->dc_func = func;
dc->dc_ctxt = ctxt;
dc->dc_group = dg;
// No fastpath/slowpath hint because we simply don't know
if (dq->dq_width != 1 && dq->do_targetq) {
return _dispatch_async_f2(dq, dc);
}
_dispatch_queue_push(dq, dc);
}
从上面的代码我们可以看出 dispatch_group_async_f 和 dispatch_async_f 相似。dispatch_group_async_f 多了 dispatch_group_enter(dg);,另外在 do_vtable 的赋值中dispatch_group_async_f 多了一个 DISPATCH_OBJ_GROUP_BIT 的标记符。既然添加了dispatch_group_enter 必定会存在 dispatch_group_leave。在之前《深入理解GCD之dispatch_queue》介绍 _dispatch_continuation_pop 函数的源码中有一段代码如下:
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
if (dg) {
//group需要进行调用dispatch_group_leave并释放信号
dispatch_group_leave(dg);
_dispatch_release(dg);
}
所以 dispatch_group_async_f 函数中的 dispatch_group_leave 是在 _dispatch_continuation_pop 函数中调用的。
这里概括一下 dispatch_group_async_f 的工作流程:
- 调用 dispatch_group_enter;
- 将block和queue等信息记录到 dispatch_continuation_t 结构体中,并将它加入到group的链表中;
- _dispatch_continuation_pop 执行时会判断任务是否为group,是的话执行完任务再调用dispatch_group_leave 以达到信号量的平衡。
dispatch_group_enter
void
dispatch_group_enter(dispatch_group_t dg)
{
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
(void)dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
}
dispatch_group_enter 将 dispatch_group_t 转换成 dispatch_semaphore_t,并调用dispatch_semaphore_wait,原子性减1后,进入等待状态直到有信号唤醒。所以说dispatch_group_enter 就是对 dispatch_semaphore_wait 的封装。
dispatch_group_leave
void
dispatch_group_leave(dispatch_group_t dg)
{
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
dispatch_atomic_release_barrier();
long value = dispatch_atomic_inc2o(dsema, dsema_value);//dsema_value原子性加1
if (slowpath(value == LONG_MIN)) {//内存溢出,由于dispatch_group_leave在dispatch_group_enter之前调用
DISPATCH_CLIENT_CRASH("Unbalanced call to dispatch_group_leave()");
}
if (slowpath(value == dsema->dsema_orig)) {//表示所有任务已经完成,唤醒group
(void)_dispatch_group_wake(dsema);
}
}
从上面的源代码中我们看到 dispatch_group_leave 将 dispatch_group_t 转换成dispatch_semaphore_t 后将 dsema_value 的值原子性加1。如果 value 为 LONG_MIN 程序crash;如果value等于 dsema_orig 表示所有任务已完成,调用 _dispatch_group_wake 唤醒group(_dispatch_group_wake 的作用和notify有关,我们会在后面介绍)。因为在 enter 的时候进行了原子性减1操作。所以在 leave 的时候需要原子性加1。
这里先说明一下 enter 和 leave 之间的关系:
dispatch_group_leave 与 dispatch_group_enter 配对使用。当调用了 dispatch_group_enter 而没有调用 dispatch_group_leave 时,由于 value 不等于 dsema_orig 不会走到唤醒逻辑,dispatch_group_notify 中的任务无法执行或者dispatch_group_wait收不到信号而卡住线程。
dispatch_group_enter 必须在 dispatch_group_leave 之前出现。当 dispatch_group_leave 比dispatch_group_enter 多调用了一次或者说在 dispatch_group_enter 之前被调用的时候,dispatch_group_leave 进行原子性加1操作,相当于 value 为 LONGMAX+1,发生数据长度溢出,变成 LONG_MIN,由于 value == LONG_MIN 成立,程序发生crash。
dispatch_group_notify
dispatch_group_notify 是 dispatch_group_notify_f 的封装,具体实现在后者。
void
dispatch_group_notify_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt,
void (*func)(void *))
{
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
struct dispatch_sema_notify_s *dsn, *prev;
//封装dispatch_continuation_t结构体
// FIXME -- this should be updated to use the continuation cache
while (!(dsn = calloc(1, sizeof(*dsn)))) {
sleep(1);
}
dsn->dsn_queue = dq;
dsn->dsn_ctxt = ctxt;
dsn->dsn_func = func;
_dispatch_retain(dq);
dispatch_atomic_store_barrier();
//将结构体放到链表尾部,如果链表为空同时设置链表头部节点并唤醒group
prev = dispatch_atomic_xchg2o(dsema, dsema_notify_tail, dsn);
if (fastpath(prev)) {
prev->dsn_next = dsn;
} else {
_dispatch_retain(dg);
(void)dispatch_atomic_xchg2o(dsema, dsema_notify_head, dsn);
if (dsema->dsema_value == dsema->dsema_orig) {//任务已经完成,唤醒group
_dispatch_group_wake(dsema);
}
}
}
所以 dispatch_group_notify 函数只是用链表把所有回调通知保存起来,等待调用。
_dispatch_group_wake
static long
_dispatch_group_wake(dispatch_semaphore_t dsema)
{
struct dispatch_sema_notify_s *next, *head, *tail = NULL;
long rval;
//将dsema的dsema_notify_head赋值为NULL,同时将之前的内容赋给head
head = dispatch_atomic_xchg2o(dsema, dsema_notify_head, NULL);
if (head) {
// snapshot before anything is notified/woken <rdar://problem/8554546>
//将dsema的dsema_notify_tail赋值为NULL,同时将之前的内容赋给tail
tail = dispatch_atomic_xchg2o(dsema, dsema_notify_tail, NULL);
}
//将dsema的dsema_group_waiters设置为0,并返回原来的值
rval = dispatch_atomic_xchg2o(dsema, dsema_group_waiters, 0);
if (rval) {
//循环调用semaphore_signal唤醒当初等待group的信号量,使得dispatch_group_wait函数返回。
// wake group waiters
#if USE_MACH_SEM
_dispatch_semaphore_create_port(&dsema->dsema_waiter_port);
do {
kern_return_t kr = semaphore_signal(dsema->dsema_waiter_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
} while (--rval);
#elif USE_POSIX_SEM
do {
int ret = sem_post(&dsema->dsema_sem);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
} while (--rval);
#endif
}
if (head) {
//获取链表,依次调用dispatch_async_f异步执行在notify函数中的任务即Block。
// async group notify blocks
do {
dispatch_async_f(head->dsn_queue, head->dsn_ctxt, head->dsn_func);
_dispatch_release(head->dsn_queue);
next = fastpath(head->dsn_next);
if (!next && head != tail) {
while (!(next = fastpath(head->dsn_next))) {
_dispatch_hardware_pause();
}
}
free(head);
} while ((head = next));
_dispatch_release(dsema);
}
return 0;
}
_dispatch_group_wake主要的作用有两个:
-
调用 semaphore_signal 唤醒当初等待group的信号量,使得 dispatch_group_wait 函数返回。
-
获取链表,依次调用 dispatch_async_f 异步执行在notify函数中的任务即Block。
到这里我们已经差不多知道了dispatch_group工作过程,我们用一张图表示:
dispatch_group.png
dispatch_group_wait
long
dispatch_group_wait(dispatch_group_t dg, dispatch_time_t timeout)
{
dispatch_semaphore_t dsema = (dispatch_semaphore_t)dg;
if (dsema->dsema_value == dsema->dsema_orig) {//没有需要执行的任务
return 0;
}
if (timeout == 0) {//返回超时
#if USE_MACH_SEM
return KERN_OPERATION_TIMED_OUT;
#elif USE_POSIX_SEM
errno = ETIMEDOUT;
return (-1);
#endif
}
return _dispatch_group_wait_slow(dsema, timeout);
}
dispatch_group_wait 用于等待group中的任务完成。
_dispatch_group_wait_slow
static long
_dispatch_group_wait_slow(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long orig;
again:
// check before we cause another signal to be sent by incrementing
// dsema->dsema_group_waiters
if (dsema->dsema_value == dsema->dsema_orig) {
return _dispatch_group_wake(dsema);
}
// Mach semaphores appear to sometimes spuriously wake up. Therefore,
// we keep a parallel count of the number of times a Mach semaphore is
// signaled (6880961).
(void)dispatch_atomic_inc2o(dsema, dsema_group_waiters);
// check the values again in case we need to wake any threads
if (dsema->dsema_value == dsema->dsema_orig) {
return _dispatch_group_wake(dsema);
}
#if USE_MACH_SEM
mach_timespec_t _timeout;
kern_return_t kr;
_dispatch_semaphore_create_port(&dsema->dsema_waiter_port);
// From xnu/osfmk/kern/sync_sema.c:
// wait_semaphore->count = -1; /* we don't keep an actual count */
//
// The code above does not match the documentation, and that fact is
// not surprising. The documented semantics are clumsy to use in any
// practical way. The above hack effectively tricks the rest of the
// Mach semaphore logic to behave like the libdispatch algorithm.
switch (timeout) {
default:
do {
uint64_t nsec = _dispatch_timeout(timeout);
_timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
_timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
kr = slowpath(semaphore_timedwait(dsema->dsema_waiter_port,
_timeout));
} while (kr == KERN_ABORTED);
if (kr != KERN_OPERATION_TIMED_OUT) {
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
// Fall through and try to undo the earlier change to
// dsema->dsema_group_waiters
case DISPATCH_TIME_NOW:
while ((orig = dsema->dsema_group_waiters)) {
if (dispatch_atomic_cmpxchg2o(dsema, dsema_group_waiters, orig,
orig - 1)) {
return KERN_OPERATION_TIMED_OUT;
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
do {
kr = semaphore_wait(dsema->dsema_waiter_port);
} while (kr == KERN_ABORTED);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
#elif USE_POSIX_SEM
//这部分代码省略
#endif
goto again;
}
从上面的代码我们发现 _dispatch_group_wait_slow 和 _dispatch_semaphore_wait_slow 的逻辑很接近。都利用mach内核的semaphore进行信号的发送。区别在于 _dispatch_semaphore_wait_slow 在等待结束后是return,而 _dispatch_group_wait_slow 在等待结束是调用 _dispatch_group_wake 去唤醒这个group。
总结
-
dispatch_group 是一个初始值为 LONG_MAX 的信号量,group中的任务完成是判断其value是否恢复成初始值。
-
dispatch_group_enter 和 dispatch_group_leave 必须成对使用并且支持嵌套。
-
如果 dispatch_group_enter 比 dispatch_group_leave 多,由于 value 不等于 dsema_orig 不会走到唤醒逻辑,dispatch_group_notify 中的任务无法执行或者 dispatch_group_wait 收不到信号而卡住线程。如果是 dispatch_group_leave 多,则会引起崩溃。









网友评论