注册

高级线程应用之栅栏、信号量、调度组以及source(五)

4.3 Dispatch Source 封装 Timer

目标是封装一个类似NSTimer的工具。

void
dispatch_source_set_timer(dispatch_source_t source,
dispatch_time_t start,
uint64_t interval,
uint64_t leeway);

  • source
    :事件源。
  • start:控制计时器第一次触发的时刻。
    • 参数类型是 dispatch_time_topaque类型),不能直接操作它。需要 dispatch_time 和 dispatch_walltime 函数来创建。
    • 常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 很常用。
    • 当使用dispatch_time 或者 DISPATCH_TIME_NOW 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用 dispatch_walltime 可以让计时器按照真实时间间隔进行计时。
  • interval:回调间隔时间。
  • leeway:计时器触发的精准程度,就算指定为0系统也无法保证完全精确的触发时间,只是会尽可能满足这个需求。

首先实现一个最简单的封装:

- (instancetype)initTimerWithTimeInterval:(NSTimeInterval)interval queue:(dispatch_queue_t)queue leeway:(NSTimeInterval)leeway repeats:(BOOL)repeats handler:(dispatch_block_t)handler {    
if (self == [super init]) {
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, leeway * NSEC_PER_SEC);
//解决与handler互相持有
__weak typeof(self) weakSelf = self;

//事件回调,这个函数在执行完之后 block 会立马执行一遍。后面隔一定时间间隔再执行一次。
dispatch_source_set_event_handler(self.timer, ^{
if (handler) {
handler();
}
if (!repeats) {
//repeats 为 NO 执行一次后取消
[weakSelf cancel];
}
});
}
return self;
}

这样就满足了最基本的要求,由于handler的调用在设置和恢复后会立马调用,所以需要过滤需改handler实现如下:
//忽略 handler 设置完马上回调
if (weakSelf.isAutoFirstCallback) {
@synchronized(weakSelf) {
weakSelf.isAutoFirstCallback = NO;
}
return;
}
//忽略挂起恢复后的立马回调
if (!weakSelf.resumeCallbackEnable && weakSelf.isResumeCallback) {
@synchronized(weakSelf) {
weakSelf.isResumeCallback = NO;
}
return;
}

if (handler) {
handler();
}

if (!repeats) {
//repeats 为 NO 执行一次后取消
[weakSelf cancel];
}

为了更灵活对注册以及取消source逻辑也进行暴露:

dispatch_source_set_registration_handler(self.timer, ^{
if (weakSelf.startBlock) {
weakSelf.startBlock();
}
});
//取消回调
dispatch_source_set_cancel_handler(self.timer, ^{
if (weakSelf.cancelBlock) {
weakSelf.cancelBlock();
}
});
由于source本身提供了挂起和恢复的功能,同样对其封装。并且需要进行释放操作,所以提供cancel功能:

- (void)start {
//为了与isResumeCallback区分开
@synchronized(self) {
if (!self.isStarted && self.timerStatus == HPTimerSuspend) {
self.isStarted = YES;
self.timerStatus = HPTimerResume;
dispatch_resume(self.timer);
}
}
}

- (void)suspend {
//挂起,挂起的时候不能设置timer为nil
@synchronized(self) {
if (self.timerStatus == HPTimerResume) {
self.timerStatus = HPTimerSuspend;
dispatch_suspend(self.timer);
}
}
}

- (void)resume {
//恢复
@synchronized(self) {
if (self.timerStatus == HPTimerSuspend) {
self.isResumeCallback = YES;
self.timerStatus = HPTimerResume;
dispatch_resume(self.timer);
}
}
}

- (void)cancel {
//取消
@synchronized(self) {
if (self.timerStatus != HPTimerCanceled) {
//先恢复再取消
if (self.timerStatus == HPTimerSuspend) {
[self resume];
}
self.timerStatus = HPTimerCanceled;
dispatch_source_cancel(self.timer);
_timer = nil;
}
}
}

- (void)dealloc {
[self cancel];
}

  • dealloc中主动进行cancel调用方可以不必在自己的dealloc中调用。

这样再暴露一些简单接口就可以直接调用了(调用方需要持有timer):

self.timer = [HPTimer scheduledTimerWithTimeInterval:3 handler:^{
NSLog(@"timer 回调");
}];

五、延迟函数(dispatch_after)

void
dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t work)
{
_dispatch_after(when, queue, NULL, work, true);
}

直接调用_dispatch_after


static inline void
_dispatch_after(dispatch_time_t when, dispatch_queue_t dq,
void *ctxt, void *handler, bool block)
{
dispatch_timer_source_refs_t dt;
dispatch_source_t ds;
uint64_t leeway, delta;
//FOREVER 直接返回什么也不做
if (when == DISPATCH_TIME_FOREVER) {
#if DISPATCH_DEBUG
DISPATCH_CLIENT_CRASH(0, "dispatch_after called with 'when' == infinity");
#endif
return;
}

delta = _dispatch_timeout(when);
if (delta == 0) {
if (block) {
//时间为0直接执行handler
return dispatch_async(dq, handler);
}
return dispatch_async_f(dq, ctxt, handler);
}
//精度 = 间隔 / 10
leeway = delta / 10; // <rdar://problem/13447496>
//<1 毫秒 的时候设置最小值为1毫秒
if (leeway < NSEC_PER_MSEC) leeway = NSEC_PER_MSEC;
//大于60s的时候设置为60s,也就是 1ms <= leeway <= 1min
if (leeway > 60 * NSEC_PER_SEC) leeway = 60 * NSEC_PER_SEC;

// this function can and should be optimized to not use a dispatch source
//创建 type 为 after 的 source
ds = dispatch_source_create(&_dispatch_source_type_after, 0, 0, dq);
dt = ds->ds_timer_refs;

dispatch_continuation_t dc = _dispatch_continuation_alloc();
if (block) {
//包装handler
_dispatch_continuation_init(dc, dq, handler, 0, 0);
} else {
_dispatch_continuation_init_f(dc, dq, ctxt, handler, 0, 0);
}
// reference `ds` so that it doesn't show up as a leak
dc->dc_data = ds;
_dispatch_trace_item_push(dq, dc);
//存储handler
os_atomic_store2o(dt, ds_handler[DS_EVENT_HANDLER], dc, relaxed);
dispatch_clock_t clock;
uint64_t target;
_dispatch_time_to_clock_and_value(when, false, &clock, &target);
if (clock != DISPATCH_CLOCK_WALL) {
leeway = _dispatch_time_nano2mach(leeway);
}
dt->du_timer_flags |= _dispatch_timer_flags_from_clock(clock);
dt->dt_timer.target = target;
dt->dt_timer.interval = UINT64_MAX;
dt->dt_timer.deadline = target + leeway;
dispatch_activate(ds);
}
  • 延时时间设置为DISPATCH_TIME_FOREVER直接返回什么也不做。
  • 延时时间为0直接调用dispatch_async执行handler
  • 精度:1ms <= leeway <= 1min要在这个范围,否则会修正。
  • 创建_dispatch_source_type_after类型的source
  • 包装存储handler
  • 调用_dispatch_time_to_clock_and_value进行target设置。

本质上 dispatch_after 也是对 source的封装。

时间单位

#define NSEC_PER_SEC 1000000000ull      1秒 = 10亿纳秒              
#define NSEC_PER_MSEC 1000000ull 1毫秒 = 100万纳秒
#define USEC_PER_SEC 1000000ull 1秒 = 100万微秒
#define NSEC_PER_USEC 1000ull 1微秒 = 1000 纳秒

1s = 1000ms = 100万us = 10亿ns
1ms = 1000us
1us = 1000ns



作者:HotPotCat
链接:https://www.jianshu.com/p/84153e072f44

0 个评论

要回复文章请先登录注册