最近在iOS開發中,需要用到單例模式,於是自己動手寫了一個,它看起來是這樣的:
<1>
+ (id)sharedInstance{ static id sharedInstance = nil; if (!sharedInstance) { sharedInstance = [[NSObject alloc] init]; } return sharedInstance;}
後來發現許多書上的做法都使用到了BOOL變數作為標值位,它看起來是這樣的:
<2>
+ (id)sharedInstance{ static id sharedInstance = nil; static BOOL token = NO; if (!token) { token = YES; sharedInstance = [[NSObject alloc] init]; } return sharedInstance;}
但是參考了蘋果官方的單例模式代碼,發現它看起來是這樣的:
<3>
+ (id)sharedInstance{ static id sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[NSObject alloc] init]; }); return sharedInstance;}
那麼它們究竟有多大區別呢?
原來,它們的區別在於多線程並發時的表現。
<1>使用了一個指標變數作為標誌位,這在多線程並發時是不可取的,因為sharedInstance = [[NSObject alloc] init];這行代碼的執行本身是需要時間的。很可能有兩個線程同時進入到了這行代碼,而這將導致記憶體流失。
<2>使用的標誌位是一個BOOL變數,當多線程並發時,會出現1線程判斷為NO,開始alloc並做賦值操作,但是2線程進入時判斷為YES,而1線程賦值操作還沒執行結束,這時候2線程會拿到一個nil。儘管它不會造成記憶體流失,但是它會有相當多的線程擷取不到對象。
<3>使用了dispatch_once函數。這個函數來自於Grand Central Dispatch (GCD),Apple自Mac OS 10.6 / iOS 4.0引用了它。
該函數接收一個dispatch_once_t用於檢查該代碼塊是否已經被調度的謂詞(是一個長整型,實際上作為BOOL使用)。它還接收一個希望在應用的生命週期內僅被調度一次的代碼塊。這不僅意味著代碼僅會被運行一次,而且還是安全執行緒的,你不需要使用諸如@synchronized之類的來防止使用多個線程或者隊列時不同步的問題。
Apple的GCD Documentation證實了這一點:
如果被多個線程調用,該函數會同步等等直至代碼塊完成。