25.dispatch_once单例实现原理

前言

日常使用单例的时候会这样用,但是你有考虑过内部具体是怎么实现的么?本文将带你通过源码探究 dispatch_once 的实现原理。

+ (instancetype)shared {
    static RYModel *object;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object = [[RYModel alloc] init];
    });
    return object;
}

一、 dispatch_once 源码解析

入参:

  • dispatch_once_t

    • 一个静态标记

  • dispatch_block_t

    • 执行的 block

1.1 dispatch_once_f

  • static dispatch_once_t onceToken 进行类型转换

  • 通过 onceToken 判断是否第一次执行

 DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    // onceToken 全局静态变量类型转换
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    // 通过 onceToken 
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    if (likely(v == DLOCK_ONCE_DONE)) {
        // 已经执行过了
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    if (_dispatch_once_gate_tryenter(l)) {// 判断是否可以执行(原子性)
        // 第一次执行
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);
}

_dispatch_once_gate_tryenter

这里尝试进入,原子性的操作,线程安全。锁在当前的线程空间。

DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
    return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}

1.2 _dispatch_once_callout

第一次执行的相关操作

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    // 执行任务
    _dispatch_client_callout(ctxt, func);
    // 广播通知执行完了
    _dispatch_once_gate_broadcast(l);
}

1.3 _dispatch_once_gate_broadcast

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
    dispatch_lock value_self = _dispatch_lock_value_for_self();
    uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    v = _dispatch_once_mark_quiescing(l);
#else
    v = _dispatch_once_mark_done(l);
#endif
    if (likely((dispatch_lock)v == value_self)) return;
    _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}

_dispatch_once_mark_done 改变标识

回头看一下 dispatch_once_f 执行过就会被直接 return 了。

参考

GCD源码:libdispatch

Last updated