iOS底層 - 銷(xiāo)毀 一個(gè) 單例
作者 阿華12年,原文發(fā)表于掘金,點(diǎn)擊閱讀原文查看作者更多文章
https://juejin.cn/post/6995020055356375077
前言
單例,我們開(kāi)發(fā)中使用很頻繁的一種設(shè)計(jì),你有沒(méi)有想過(guò),
為什么其會(huì)在app生命周期中只執(zhí)行一次?
系統(tǒng)底層做了哪些事情來(lái)實(shí)現(xiàn)的呢?
再一點(diǎn),單例可不可以銷(xiāo)毀呢?
帶著這些疑問(wèn),我們開(kāi)始今天的內(nèi)容。
單例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
class = [[self alloc] init];
});
單利中最重要的兩個(gè)參數(shù) 一個(gè)是 onceToken, 一個(gè)是 block。
核心內(nèi)容有兩點(diǎn):
為什么單利會(huì)調(diào)用一次?
單利的這個(gè)block為什么會(huì)進(jìn)行調(diào)用?
首先,我們先寫(xiě)一個(gè)單例來(lái)斷點(diǎn)調(diào)試看一下:
+ (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;
}
在執(zhí)行到 dispatch_once 函數(shù)的 block 中的時(shí)候,我們 bt 下看下堆棧信息:

我們第一次調(diào)用 sharSM 方法的時(shí)候 程序執(zhí)行來(lái)到了:
_dispatch_once_callout -> _dispatch_client_callout
我們之前有過(guò)GCD源碼探索的經(jīng)驗(yàn),明顯這個(gè) block 是在 dispatchclient_callout 函數(shù)中調(diào)用執(zhí)行了。此處的堆棧信息也是進(jìn)一步做了驗(yàn)證。
可以看到程序執(zhí)行的過(guò)程中, 參數(shù) predicate 的值:
最開(kāi)始是 0 ;
block 執(zhí)行過(guò)程中 變?yōu)榱?256;
最后在return 之前 變?yōu)榱?-1.
接下來(lái),我們就看一下 diapatch_once 函數(shù)的底層源碼實(shí)現(xiàn):
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);
// 狀態(tài)為 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
// 第一次進(jìn)來(lái) 獲取鎖, 原子操作多線(xiàn)程處理
if (_dispatch_once_gate_tryenter(l)) {
//執(zhí)行調(diào)用
return _dispatch_once_callout(l, ctxt, func);
}
// 有鎖鎖住的話(huà) 會(huì) 等待開(kāi)鎖
return _dispatch_once_wait(l);
}
跟著 dispatch_once_f 函數(shù)內(nèi)部流程我們解釋一下,調(diào)用一次是因?yàn)椋簝?nèi)部實(shí)現(xiàn)的 val (也就是 static dispatch_once_t predicate )底層會(huì)封裝成dispatch_once_gate_t , 這個(gè)變量用來(lái)獲取底層原子性的一個(gè)關(guān)聯(lián)。關(guān)聯(lián)一個(gè) uintptr_t 類(lèi)型 v的一個(gè)變量,用來(lái)查詢(xún)。當(dāng)前的 onceToken是一個(gè)全局的靜態(tài)變量。根據(jù)每個(gè)單利不同,每個(gè)靜態(tài)變量也不同。為了保證唯一性,在底層使用類(lèi)似KVC的形式通過(guò) os_atomic_load 出來(lái)。如果取出來(lái)的值為 DLOCK_ONCE_DONE 了:已經(jīng)處理過(guò)一次了,就retune返回出去了。當(dāng)?shù)谝淮未a執(zhí)行進(jìn)來(lái)的時(shí)候:為了保證線(xiàn)程的安全性把自己鎖起來(lái),保證當(dāng)前任務(wù)執(zhí)行的唯一,防止相同的onceToken進(jìn)行多次執(zhí)行。鎖住之后進(jìn)行 block 的調(diào)用執(zhí)行。調(diào)用完畢后將鎖解開(kāi),于此同時(shí)會(huì)將 v 的值 置為 DLOCK_ONCE_DONE(下次就不會(huì)在進(jìn)入到調(diào)用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);
// 處理完成之后 進(jìn)行廣播
_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)
{
// 先匹配 再改變?yōu)?DLOCK_ONCE_DONE 的狀態(tài)
// 下次 判斷為 DLOCK_ONCE_DONE 狀態(tài),就無(wú)法進(jìn)來(lái)
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);
}
如何銷(xiāo)毀 ?
我們稍微修改下單例的內(nèi)部實(shí)現(xiàn):
+ (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;
}

通過(guò)日志內(nèi)容可以發(fā)現(xiàn):修改predicate 的 值 之后,每一次都會(huì)初始化一個(gè)新的對(duì)象。那么, 我們就可以通過(guò)設(shè)置predicate的值,來(lái)達(dá)到控制單例初始化次數(shù)的目的。

那么, dealocSM 方法如何實(shí)現(xiàn)的呢?
+ (void)deallocSM {
predicate = 0;
class = nil;
}
同時(shí) , 這兩部分需要 從 shareSM 中提取出來(lái),放到類(lèi)中:
static SMObject *class = nil;
static dispatch_once_t predicate;
這樣,就可以實(shí)現(xiàn)單例的銷(xiāo)毀。
線(xiàn)程安全嗎?
_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);
}
這里是原子操作,對(duì)于鎖的處理,并且對(duì)線(xiàn)程操作進(jìn)行控制。_dispatch_lock_value_for_self 對(duì)于當(dāng)前自己隊(duì)列中的線(xiàn)程空間的鎖,防止多線(xiàn)程操作 。為了保證線(xiàn)程的安全性把自己鎖起來(lái),保證當(dāng)前任務(wù)執(zhí)行的唯一,防止相同的onceToken進(jìn)行多次執(zhí)行。是對(duì)多線(xiàn)程的封裝處理。
