概述 Dispatch Source是BSD系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。它的CPU负荷非常小,尽量不占用资源。当事件发生时,Dispatch Source会在指定的Dispatch Queue中执行事件的处理。
使用篇 dispatch_source最常见的用法就是用来实现定时器,代码如下:
1 2 3 4 5 6 7 8 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, 0), 3 * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(source, ^{ //定时器触发时执行 NSLog(@"timer响应了"); }); //启动timer dispatch_resume(source);
Dispatch Source
定时器的代码看似很简单,但其实是GCD中坑最多的API了,如果处理不好很容易引起Crash。关于Dispatch Source
定时器需要注意的知识点请参考文章最后的总结篇。
原理篇 dispatch_source_create dispatch_source_create
函数用来创建dispatch_source_t对象,简化后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t q) { //申请内存空间 ds = _dispatch_alloc(DISPATCH_VTABLE(source), sizeof(struct dispatch_source_s)); //初始化ds _dispatch_queue_init((dispatch_queue_t)ds); ds->dq_label = "source"; ds->do_ref_cnt++; // the reference the manager queue holds ds->do_ref_cnt++; // since source is created suspended //默认处于暂状态,需要手动调用resume ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL; ds->do_targetq = &_dispatch_mgr_q; // First item on the queue sets the user-specified target queue //设置事件回调的队列 dispatch_set_target_queue(ds, q); _dispatch_object_debug(ds, "%s", __func__); return ds; }
dispatch_source_set_timer dispatch_source_set_timer实际上调用了_dispatch_source_set_timer,看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static inline void _dispatch_source_set_timer(dispatch_source_t ds, dispatch_time_t start, uint64_t interval, uint64_t leeway, bool source_sync) { //首先屏蔽非timer类型的source if (slowpath(!ds->ds_is_timer) || slowpath(ds_timer(ds->ds_refs).flags & DISPATCH_TIMER_INTERVAL)) { DISPATCH_CLIENT_CRASH("Attempt to set timer on a non-timer source"); } //创建dispatch_set_timer_params结构体绑定source和timer参数 struct dispatch_set_timer_params *params; params = _dispatch_source_timer_params(ds, start, interval, leeway); _dispatch_source_timer_telemetry(ds, params->ident, ¶ms->values); dispatch_retain(ds); if (source_sync) { //将source当做队列使用,执行dispatch_barrier_async_f压入队列, //核心函数为_dispatch_source_set_timer2 return _dispatch_barrier_trysync_f((dispatch_queue_t)ds, params, _dispatch_source_set_timer2); } else { return _dispatch_source_set_timer2(params); } }
_dispatch_source_set_timer
实际上是调用了_dispatch_source_set_timer2
函数:
1 2 3 4 5 6 7 8 9 static void _dispatch_source_set_timer2(void *context) { // Called on the source queue struct dispatch_set_timer_params *params = context; //暂停队列,避免修改过程中定时器被触发了。 dispatch_suspend(params->ds); //在_dispatch_mgr_q队列上执行_dispatch_source_set_timer3(params) dispatch_barrier_async_f(&_dispatch_mgr_q, params, _dispatch_source_set_timer3); }
_dispatch_source_set_timer2
函数的逻辑是在_dispatch_mgr_q队列执行_dispatch_source_set_timer3(params)
,接下来的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void _dispatch_source_set_timer3(void *context) { // Called on the _dispatch_mgr_q struct dispatch_set_timer_params *params = context; dispatch_source_t ds = params->ds; ds->ds_ident_hack = params->ident; ds_timer(ds->ds_refs) = params->values; ds->ds_pending_data = 0; (void)dispatch_atomic_or2o(ds, ds_atomic_flags, DSF_ARMED, release); //恢复队列,对应着_dispatch_source_set_timer2函数中的dispatch_suspend dispatch_resume(ds); // Must happen after resume to avoid getting disarmed due to suspension //根据下一次触发时间将timer进行排序 _dispatch_timers_update(ds); dispatch_release(ds); if (params->values.flags & DISPATCH_TIMER_WALL_CLOCK) { _dispatch_mach_host_calendar_change_register(); } free(params); }
当执行提交到_dispatch_mgr_q队列的block时,会调用&_dispatch_mgr_q->do_invoke函数,即&_dispatch_mgr_q的vtable中定义的_dispatch_mgr_thread
。接下来会走到_dispatch_mgr_invoke
函数。在这个函数里用I/O多路复用功能的select来实现定时器功能:
1 2 r = select(FD_SETSIZE, &tmp_rfds, &tmp_wfds, NULL, poll ? (struct timeval*)&timeout_immediately : NULL);
当内层的 _dispatch_mgr_q
队列被唤醒后,还会进一步唤醒外层的队列(当初用户指定的那个),并在指定队列上执行 timer 触发时的 block。
dispatch_source_set_event_handler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void dispatch_source_set_event_handler(dispatch_source_t ds, dispatch_block_t handler) { //将block进行copy后压入到队列中 handler = _dispatch_Block_copy(handler); _dispatch_barrier_trysync_f((dispatch_queue_t)ds, handler, _dispatch_source_set_event_handler2); } static void _dispatch_source_set_event_handler2(void *context) { dispatch_source_t ds = (dispatch_source_t)_dispatch_queue_get_current(); dispatch_assert(dx_type(ds) == DISPATCH_SOURCE_KEVENT_TYPE); dispatch_source_refs_t dr = ds->ds_refs; if (ds->ds_handler_is_block && dr->ds_handler_ctxt) { Block_release(dr->ds_handler_ctxt); } //设置上下文,保存提交的block等信息 dr->ds_handler_func = context ? _dispatch_Block_invoke(context) : NULL; dr->ds_handler_ctxt = context; ds->ds_handler_is_block = true; }
dispatch_source_set_cancel_handler dispatch_source_set_cancel_handler
与dispatch_source_set_event_handler
功能类似,保存一下取消事件处理的上下文信息。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void dispatch_source_set_cancel_handler(dispatch_source_t ds, dispatch_block_t handler) { //将block进行copy后压入到队列中 handler = _dispatch_Block_copy(handler); _dispatch_barrier_trysync_f((dispatch_queue_t)ds, handler, _dispatch_source_set_cancel_handler2); } static void _dispatch_source_set_cancel_handler2(void *context) { dispatch_source_t ds = (dispatch_source_t)_dispatch_queue_get_current(); dispatch_assert(dx_type(ds) == DISPATCH_SOURCE_KEVENT_TYPE); dispatch_source_refs_t dr = ds->ds_refs; if (ds->ds_cancel_is_block && dr->ds_cancel_handler) { Block_release(dr->ds_cancel_handler); } //保存事件取消的信息 dr->ds_cancel_handler = context; ds->ds_cancel_is_block = true; }
dispatch_resume/dispatch_suspend 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 //恢复 void dispatch_resume(dispatch_object_t dou) { DISPATCH_OBJECT_TFB(_dispatch_objc_resume, dou); // Global objects cannot be suspended or resumed. if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT) || slowpath(dx_type(dou._do) == DISPATCH_QUEUE_ROOT_TYPE)) { return; } //将do_suspend_cnt原子性减二,并返回之前存储的值 unsigned int suspend_cnt = dispatch_atomic_sub_orig2o(dou._do, do_suspend_cnt, DISPATCH_OBJECT_SUSPEND_INTERVAL, relaxed); if (fastpath(suspend_cnt > DISPATCH_OBJECT_SUSPEND_INTERVAL)) { return _dispatch_release(dou._do); } if (fastpath(suspend_cnt == DISPATCH_OBJECT_SUSPEND_INTERVAL)) { _dispatch_wakeup(dou._do); return _dispatch_release(dou._do); } DISPATCH_CLIENT_CRASH("Over-resume of an object"); } //暂停 void dispatch_suspend(dispatch_object_t dou) { DISPATCH_OBJECT_TFB(_dispatch_objc_suspend, dou); if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT) || slowpath(dx_type(dou._do) == DISPATCH_QUEUE_ROOT_TYPE)) { return; } //将do_suspend_cnt原子性加二 (void)dispatch_atomic_add2o(dou._do, do_suspend_cnt, DISPATCH_OBJECT_SUSPEND_INTERVAL, relaxed); _dispatch_retain(dou._do); }
判断队列是否暂停的依据是do_suspend_cnt是否大于等于2,全局队列和主队列默认都是小于2的,即处于启动状态。 而dispatch_source_create方法中,do_suspend_cnt初始为DISPATCH_OBJECT_SUSPEND_INTERVAL,即默认处于暂停状态,需要手动调用resume开启。 代码定义如下:
1 2 3 4 #define DISPATCH_OBJECT_SUSPEND_LOCK 1u #define DISPATCH_OBJECT_SUSPEND_INTERVAL 2u #define DISPATCH_OBJECT_SUSPENDED(x) \ ((x)->do_suspend_cnt >= DISPATCH_OBJECT_SUSPEND_INTERVAL)
dispatch_after dispatch_after
是基于Dispatch Source的定时器实现的,函数内部直接调用dispatch_after_f
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *ctxt, dispatch_function_t func) { uint64_t delta, leeway; dispatch_source_t ds; //屏蔽DISPATCH_TIME_FOREVER类型 if (when == DISPATCH_TIME_FOREVER) { #if DISPATCH_DEBUG DISPATCH_CLIENT_CRASH( "dispatch_after_f() called with 'when' == infinity"); #endif return; } delta = _dispatch_timeout(when); if (delta == 0) { return dispatch_async_f(queue, ctxt, func); } leeway = delta / 10; // <rdar://problem/13447496> if (leeway < NSEC_PER_MSEC) leeway = NSEC_PER_MSEC; if (leeway > 60 * NSEC_PER_SEC) leeway = 60 * NSEC_PER_SEC; // this function can and should be optimized to not use a dispatch source //创建dispatch_source ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_assert(ds); dispatch_continuation_t dc = _dispatch_continuation_alloc(); dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT); dc->dc_func = func; dc->dc_ctxt = ctxt; dc->dc_data = ds; //将dispatch_continuation_t存储到上下文中 dispatch_set_context(ds, dc); //设置timer并启动 dispatch_source_set_event_handler_f(ds, _dispatch_after_timer_callback); dispatch_source_set_timer(ds, when, DISPATCH_TIME_FOREVER, leeway); dispatch_resume(ds); }
timer到时之后,会调用_dispatch_after_timer_callback
函数,在这里取出上下文里的block并执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 void _dispatch_after_timer_callback(void *ctxt) { dispatch_continuation_t dc = ctxt, dc1; dispatch_source_t ds = dc->dc_data; dc1 = _dispatch_continuation_free_cacheonly(dc); //执行任务的block并执行 _dispatch_client_callout(dc->dc_ctxt, dc->dc_func); //清理数据 dispatch_source_cancel(ds); dispatch_release(ds); if (slowpath(dc1)) { _dispatch_continuation_free_to_cache_limit(dc1); } }
总结篇 Dispatch Source使用最多的就是用来实现定时器,source创建后默认是暂停状态,需要手动调用dispatch_resume
启动定时器。dispatch_after
只是封装调用了dispatch source定时器,然后在回调函数中执行定义的block。
Dispatch Source定时器使用时也有一些需要注意的地方,不然很可能会引起crash:
1、循环引用:因为dispatch_source_set_event_handler回调是个block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了self,self又持有了source的话,就会引起循环引用。正确的方法是使用weak+strong或者提前调用dispatch_source_cancel取消timer。
2、dispatch_resume
和dispatch_suspend
调用次数需要平衡,如果重复调用dispatch_resume则会崩溃,因为重复调用会让dispatch_resume
代码里if分支不成立,从而执行了DISPATCH_CLIENT_CRASH(“Over-resume of an object”)导致崩溃。
3、source在suspend状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在resume状态下调用dispatch_source_cancel(source)后再重新创建。
结语 通过阅读GCD的源码还是学到了很多知识,也加深了工作中对GCD的使用和理解,希望通过GCD源码解析的文章和大家一起探讨相互学习。
最后打个广告,有想来滴滴顺风车一起工作学习的小伙伴请发简历到chenaibin@didichuxing.com ,期待和优秀的你一起共事!