前言
單例,我們開發中使用很頻繁的一種設計,你有沒有想過,
- 為什么其會在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進行多次執行。是對多線程的封裝處理。