美文网首页
GCD部分API学习笔记

GCD部分API学习笔记

作者: 寻心_0a46 | 来源:发表于2019-08-08 21:08 被阅读0次

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_canceldispatch_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_fdispatch_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 的工作流程:

  1. 调用 dispatch_group_enter
  2. 将block和queue等信息记录到 dispatch_continuation_t 结构体中,并将它加入到group的链表中;
  3. _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_enterdispatch_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_leavedispatch_group_t 转换成dispatch_semaphore_t 后将 dsema_value 的值原子性加1。如果 valueLONG_MIN 程序crash;如果value等于 dsema_orig 表示所有任务已完成,调用 _dispatch_group_wake 唤醒group(_dispatch_group_wake 的作用和notify有关,我们会在后面介绍)。因为在 enter 的时候进行了原子性减1操作。所以在 leave 的时候需要原子性加1。

这里先说明一下 enterleave 之间的关系:

dispatch_group_leavedispatch_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_leavedispatch_group_enter 多调用了一次或者说在 dispatch_group_enter 之前被调用的时候,dispatch_group_leave 进行原子性加1操作,相当于 valueLONGMAX+1,发生数据长度溢出,变成 LONG_MIN,由于 value == LONG_MIN 成立,程序发生crash。

dispatch_group_notify

dispatch_group_notifydispatch_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主要的作用有两个:

  1. 调用 semaphore_signal 唤醒当初等待group的信号量,使得 dispatch_group_wait 函数返回。

  2. 获取链表,依次调用 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。

总结

  1. dispatch_group 是一个初始值为 LONG_MAX 的信号量,group中的任务完成是判断其value是否恢复成初始值。

  2. dispatch_group_enterdispatch_group_leave 必须成对使用并且支持嵌套。

  3. 如果 dispatch_group_enterdispatch_group_leave 多,由于 value 不等于 dsema_orig 不会走到唤醒逻辑,dispatch_group_notify 中的任务无法执行或者 dispatch_group_wait 收不到信号而卡住线程。如果是 dispatch_group_leave 多,则会引起崩溃。

相关文章

  • GCD部分API学习笔记

    GCD定时器 IOS开发中常用的定时器之一,以纳秒作为时间间隔单位,且运行不受RunLoop的运行模式的影响,相比...

  • 关于gcd

    这几天有点时间,就把gcd研究了一下。现把心得和笔记记录如下 gcd常用的api,dispatch_queue_c...

  • ARKit部分API翻译

    ARKit部分API翻译 说明:学习AR查阅API翻译的,有点粗糙,个人笔记,别吐槽 目录 ARSessionDe...

  • GCD的API学习

    全称是Grand Central Dispatch,是异步执行任务的技术之一.将任务(所要做的操作)追加到队列中,...

  • GCD相关知识点

    GCD相关知识点 多线程技术--GCD 深入学习GCD GCD 深入理解:第一部分 GCD 深入理解:第二部分 1...

  • iOS 学习笔记一 多线程之NSOperation

    一、定义: GCD是基于C的底层API,而NSOperation则是封装GCD实现Objective-C API ...

  • iOS-GCD笔记

    GCD笔记 总结一下多线程部分,最强大的无疑是GCD,那么先从这一块部分讲起. Dispatch Queue的种类...

  • iOS进阶-GCD信号量-dispatch_semaphore_

    目录 GCD信号量概念 使用API 实例使用 GCD信号量概念 使用API : dispatch_semaphor...

  • Swift 3必看:从使用场景了解GCD新API

    Swift 3必看:从使用场景了解GCD新API Swift 3必看:从使用场景了解GCD新API

  • iOS进阶-多线程-GCD

    GCD 概念 GCD队列 使用总结 实用api 实用遇到的坑 一、 概念 GCD(grand central di...

网友评论

      本文标题:GCD部分API学习笔记

      本文链接:https://www.haomeiwen.com/subject/wxutjctx.html