GCD之dispatch_source

概述

Dispatch SourceBSD系統(tǒng)內(nèi)核慣有功能kqueue的包裝,kqueue是在XNU內(nèi)核中發(fā)生各種事件時,在應(yīng)用程序編程方執(zhí)行處理的技術(shù)。它的CPU負(fù)荷非常小,盡量不占用資源。當(dāng)事件發(fā)生時,Dispatch Source會在指定的Dispatch Queue中執(zhí)行事件的處理。

使用篇

dispatch_source最常見的用法就是用來實(shí)現(xiàn)定時器,代碼如下:

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, ^{
    //定時器觸發(fā)時執(zhí)行
   NSLog(@"timer響應(yīng)了");
});
//啟動timer
dispatch_resume(source);

Dispatch Source定時器的代碼看似很簡單,但其實(shí)是GCD中坑最多的API了,如果處理不好很容易引起Crash。關(guān)于Dispatch Source定時器需要注意的知識點(diǎn)請參考文章最后的總結(jié)篇。

原理篇

dispatch_source_create

dispatch_source_create函數(shù)用來創(chuàng)建dispatch_source_t對象,簡化后的代碼如下:

dispatch_source_t dispatch_source_create(dispatch_source_type_t type,
    uintptr_t handle,
    unsigned long mask,
    dispatch_queue_t q) {
    //申請內(nèi)存空間
    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
    //默認(rèn)處于暫狀態(tài),需要手動調(diào)用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
    //設(shè)置事件回調(diào)的隊列
    dispatch_set_target_queue(ds, q);
    _dispatch_object_debug(ds, "%s", __func__);
    return ds;
}

dispatch_source_set_timer

dispatch_source_set_timer實(shí)際上調(diào)用了_dispatch_source_set_timer,看一下代碼:

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");
    }
    //創(chuàng)建dispatch_set_timer_params結(jié)構(gòu)體綁定source和timer參數(shù)
    struct dispatch_set_timer_params *params;
    params = _dispatch_source_timer_params(ds, start, interval, leeway);
    _dispatch_source_timer_telemetry(ds, params->ident, &params->values);
    dispatch_retain(ds);
    if (source_sync) {
       //將source當(dāng)做隊列使用,執(zhí)行dispatch_barrier_async_f壓入隊列,
       //核心函數(shù)為_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實(shí)際上是調(diào)用了_dispatch_source_set_timer2函數(shù):

static void _dispatch_source_set_timer2(void *context) {
    // Called on the source queue
    struct dispatch_set_timer_params *params = context;
    //暫停隊列,避免修改過程中定時器被觸發(fā)了。
    dispatch_suspend(params->ds);
    //在_dispatch_mgr_q隊列上執(zhí)行_dispatch_source_set_timer3(params)
    dispatch_barrier_async_f(&_dispatch_mgr_q, params,
            _dispatch_source_set_timer3);
}

_dispatch_source_set_timer2函數(shù)的邏輯是在_dispatch_mgr_q隊列執(zhí)行_dispatch_source_set_timer3(params),接下來的邏輯如下:

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);
    //恢復(fù)隊列,對應(yīng)著_dispatch_source_set_timer2函數(shù)中的dispatch_suspend
    dispatch_resume(ds);
    // Must happen after resume to avoid getting disarmed due to suspension
    //根據(jù)下一次觸發(fā)時間將timer進(jìn)行排序
    _dispatch_timers_update(ds);
    dispatch_release(ds);
    if (params->values.flags & DISPATCH_TIMER_WALL_CLOCK) {
        _dispatch_mach_host_calendar_change_register();
    }
    free(params);
}

當(dāng)執(zhí)行提交到_dispatch_mgr_q隊列的block時,會調(diào)用&_dispatch_mgr_q->do_invoke函數(shù),即&_dispatch_mgr_q的vtable中定義的_dispatch_mgr_thread。接下來會走到_dispatch_mgr_invoke函數(shù)。在這個函數(shù)里用I/O多路復(fù)用功能的select來實(shí)現(xiàn)定時器功能:

r = select(FD_SETSIZE, &tmp_rfds, &tmp_wfds, NULL,
            poll ? (struct timeval*)&timeout_immediately : NULL);


當(dāng)內(nèi)層的 _dispatch_mgr_q 隊列被喚醒后,還會進(jìn)一步喚醒外層的隊列(當(dāng)初用戶指定的那個),并在指定隊列上執(zhí)行 timer 觸發(fā)時的 block。

dispatch_source_set_event_handler

void dispatch_source_set_event_handler(dispatch_source_t ds,
        dispatch_block_t handler) {
    //將block進(jìn)行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);
    }
    //設(shè)置上下文,保存提交的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_handlerdispatch_source_set_event_handler功能類似,保存一下取消事件處理的上下文信息。代碼如下:

void dispatch_source_set_cancel_handler(dispatch_source_t ds,
    dispatch_block_t handler) {
    //將block進(jìn)行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

//恢復(fù)
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);
}

判斷隊列是否暫停的依據(jù)是do_suspend_cnt是否大于等于2,全局隊列和主隊列默認(rèn)都是小于2的,即處于啟動狀態(tài)。
而dispatch_source_create方法中,do_suspend_cnt初始為DISPATCH_OBJECT_SUSPEND_INTERVAL,即默認(rèn)處于暫停狀態(tài),需要手動調(diào)用resume開啟。
代碼定義如下:

#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的定時器實(shí)現(xiàn)的,函數(shù)內(nèi)部直接調(diào)用dispatch_after_f,代碼如下:

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
    //創(chuàng)建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);
    //設(shè)置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到時之后,會調(diào)用_dispatch_after_timer_callback函數(shù),在這里取出上下文里的block并執(zhí)行:

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);
    //執(zhí)行任務(wù)的block并執(zhí)行
    _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
    //清理數(shù)據(jù)
    dispatch_source_cancel(ds);
    dispatch_release(ds);
    if (slowpath(dc1)) {
        _dispatch_continuation_free_to_cache_limit(dc1);
    }
}

總結(jié)篇

Dispatch Source使用最多的就是用來實(shí)現(xiàn)定時器,source創(chuàng)建后默認(rèn)是暫停狀態(tài),需要手動調(diào)用dispatch_resume啟動定時器。dispatch_after只是封裝調(diào)用了dispatch source定時器,然后在回調(diào)函數(shù)中執(zhí)行定義的block。

Dispatch Source定時器使用時也有一些需要注意的地方,不然很可能會引起crash:

1、循環(huán)引用:因?yàn)閐ispatch_source_set_event_handler回調(diào)是個block,在添加到source的鏈表上時會執(zhí)行copy并被source強(qiáng)引用,如果block里持有了self,self又持有了source的話,就會引起循環(huán)引用。正確的方法是使用weak+strong或者提前調(diào)用dispatch_source_cancel取消timer。

2、dispatch_resumedispatch_suspend調(diào)用次數(shù)需要平衡,如果重復(fù)調(diào)用dispatch_resume則會崩潰,因?yàn)橹貜?fù)調(diào)用會讓dispatch_resume代碼里if分支不成立,從而執(zhí)行了DISPATCH_CLIENT_CRASH(“Over-resume of an object”)導(dǎo)致崩潰。

3、source在suspend狀態(tài)下,如果直接設(shè)置source = nil或者重新創(chuàng)建source都會造成crash。正確的方式是在resume狀態(tài)下調(diào)用dispatch_source_cancel(source)后再重新創(chuàng)建。

結(jié)語

通過閱讀GCD的源碼還是學(xué)到了很多知識,也加深了工作中對GCD的使用和理解,希望通過GCD源碼解析的文章和大家一起探討相互學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容