扣丁書屋

iOS底層 - 銷毀 一個 單例

前言

單例,我們開發中使用很頻繁的一種設計,你有沒有想過,

  • 為什么其會在app生命周期中只執行一次?
  • 系統底層做了哪些事情來實現的呢?
  • 再一點,單例可不可以銷毀呢?

帶著這些疑問,我們開始今天的內容。

單例

static dispatch_once_t onceToken;   
dispatch_once(&onceToken, ^{  
    class = [[self alloc] init];
});

單利中最重要的兩個參數 一個是 onceToken, 一個是 block。

核心內容有兩點:

  • 為什么單利會調用一次?
  • 單利的這個block為什么會進行調用?

首先,我們先寫一個單例來斷點調試看一下:

+ (instancetype)shareSM {

    static SMObject *class = nil;
    static dispatch_once_t predicate;

    NSLog(@"1:%ld", predicate);

    dispatch_once(&predicate, ^{
        NSLog(@"2:%ld", predicate);
        class = [[SMObject alloc] init];
    });

    NSLog(@"3:%ld", predicate);
    return class;
}

在執行到 dispatch_once 函數的 block 中的時候,我們 bt 下看下堆棧信息:

我們第一次調用 sharSM 方法的時候 程序執行來到了:

_dispatch_once_callout -> _dispatch_client_callout

我們之前有過GCD源碼探索的經驗,明顯這個 block 是在 dispatchclient_callout 函數中調用執行了。此處的堆棧信息也是進一步做了驗證。 可以看到程序執行的過程中, 參數 predicate 的值:

  • 最開始是 0 ;
  • block 執行過程中 變為了 256;
  • 最后在return 之前 變為了 -1.

接下來,我們就看一下 diapatch_once 函數的底層源碼實現:

dispatch_once

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}

dispatch_once_f

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);

        // 狀態為 DLOCK_ONCE_DONE 直接返回
    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_f 函數內部流程我們解釋一下,調用一次是因為:內部實現的 val (也就是 static dispatch_once_t predicate )底層會封裝成dispatch_once_gate_t , 這個變量用來獲取底層原子性的一個關聯。關聯一個 uintptr_t 類型 v的一個變量,用來查詢。當前的 onceToken是一個全局的靜態變量。根據每個單利不同,每個靜態變量也不同。為了保證唯一性,在底層使用類似KVC的形式通過 os_atomic_load 出來。如果取出來的值為 DLOCK_ONCE_DONE 了:已經處理過一次了,就retune返回出去了。當第一次代碼執行進來的時候:為了保證線程的安全性把自己鎖起來,保證當前任務執行的唯一,防止相同的onceToken進行多次執行。鎖住之后進行 block 的調用執行。調用完畢后將鎖解開,于此同時會將 v 的值 置為 DLOCK_ONCE_DONE(下次就不會在進入到調用block流程)。所以保證了單利的唯一性。

_dispatch_once_callout

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);
}

...

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);
}

...

static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    // 先匹配 再改變為 DLOCK_ONCE_DONE 的狀態
    // 下次 判斷為 DLOCK_ONCE_DONE 狀態,就無法進來
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

_dispatch_once_gate_broadcast

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);
}

...

static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

如何銷毀 ?

我們稍微修改下單例的內部實現:

+ (instancetype)shareSM {

    static SMObject *class = nil;
    static dispatch_once_t predicate;

    NSLog(@"1:%ld", predicate);
    predicate = 0;
    NSLog(@"1-1        :%ld", predicate);

    dispatch_once(&predicate, ^{
        NSLog(@"2:%ld", predicate);
        class = [[SMObject alloc] init];
    });

    NSLog(@"3:%ld", predicate);
    return class;
}

通過日志內容可以發現:修改predicate 的 值 之后,每一次都會初始化一個新的對象。那么, 我們就可以通過設置predicate的值,來達到控制單例初始化次數的目的。

那么, dealocSM 方法如何實現的呢?

+ (void)deallocSM {

    predicate = 0;
    class = nil;
}

同時 , 這兩部分需要 從 shareSM 中提取出來,放到類中:

static SMObject *class = nil;
static dispatch_once_t predicate;

這樣,就可以實現單例的銷毀。

線程安全嗎?

_dispatch_once_gate_tryenter(l)

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);
}

這里是原子操作,對于鎖的處理,并且對線程操作進行控制。_dispatch_lock_value_for_self 對于當前自己隊列中的線程空間的鎖,防止多線程操作 。為了保證線程的安全性把自己鎖起來,保證當前任務執行的唯一,防止相同的onceToken進行多次執行。是對多線程的封裝處理。


https://mp.weixin.qq.com/s/q1RYuCUxsHyftoSMWYhZNg

最多閱讀

iOS 性能檢測新方式?——AnimationHitches 8月以前  |  19697次閱讀
快速配置 Sign In with Apple 2年以前  |  5526次閱讀
APP適配iOS11 3年以前  |  4466次閱讀
App Store 審核指南[2017年最新版本] 3年以前  |  4296次閱讀
所有iPhone設備尺寸匯總 3年以前  |  4219次閱讀
使用 GPUImage 實現一個簡單相機 3年以前  |  3944次閱讀
開篇 關于iOS越獄開發 3年以前  |  3813次閱讀
在越獄的iPhone設置上使用lldb調試 3年以前  |  3739次閱讀
給數組NSMutableArray排序 3年以前  |  3667次閱讀
UITableViewCell高亮效果實現 3年以前  |  3367次閱讀
使用ssh訪問越獄iPhone的兩種方式 3年以前  |  3363次閱讀
關于Xcode不能打印崩潰日志 3年以前  |  3261次閱讀
使用ssh 訪問越獄iPhone的兩種方式 3年以前  |  3101次閱讀
為對象添加一個釋放時觸發的block 3年以前  |  2877次閱讀
使用最高權限操作iPhone手機 3年以前  |  2849次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>