《編寫高質量iOS與OS X代碼的52個有效方法》--第六章 第45條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)
第45條:使用dispatch_once來執行只需運行一次的線程安全代碼
單例模式(singleton)對Objective-C開發者來說并不陌生,常見的實現方式為:在類中編寫名為sharedInstance的方法,該方法只會返回全類共用的單例實例,而不會在每次調用時都創建新的實例。共享實例的方法一般又會這樣寫:
@implementation EOCClass
+ (instancetype)sharedInstance
{
static EOCClass *sharedInstance = nil;
@synchronized (self) {
if (!sharedInstance) {
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
@end
筆者發現單例模式容易引起激烈爭論,Objective-C的單例尤其如此。線程安全是大家爭論的主要問題。為保證線程安全,上述代碼將創建單例實例的代碼包裹在同步塊里。
不過GCD引入了一項特性,能使單例實現起來更為容易。所用的函數是:
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
此函數接受類型為dispatch_once_t的特殊參數,筆者稱其為“標記”(token),此外還接受參數。對于給定的標記來說,該函數保證相關的塊必定會執行,且僅執行一次。首次調用該函數時,必然會執行塊中的代碼,最重要的一點在于,此操作完全是線程安全的。請注意,對于只需執行一次的塊來說,每次調用函數時傳入的標記都必須完全相同。因此,開發者通常將標記變量聲明在static或global作用域里。
剛才實現單例模式所用的sharedInstance方法,可以用此函數來改寫:
+ (instancetype)sharedInstance
{
static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
使用dispatch_once可以簡化代碼并且徹底保證線程安全,開發者根本無須擔心加鎖或同步。所有問題都有GCD在底層處理。由于每次調用時都必須使用完全相同的標記,所以標記要聲明成static。把該變量定義在static作用域中,可以保證編譯器在每次執行shareInstance方法時都會復用這個變量,而不會創建新變量。
此外,dispatch_once更高效。他沒有使用重量級的同步機制,若是那樣的話,每次運行代碼前都要獲取鎖,相反,此函數采用“原子訪問”(atomic access)來查詢標記,以判斷其所對應的代碼原來是否已經執行過。
要點
- 經常需要編寫“只需執行一次的線程安全代碼”(thread-safe single-code execution)。通過GCD所提供的dispatch_once函數,很容易就能實現此功能。
- 標記應該聲明在static或global作用域中,這樣的話,在把只需執行一次的塊傳給dispatch_once函數時,穿進去的標記也是相同的。