使用dispatch_once實現單例模式

單例模式是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例類的特殊類。經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。若是但願在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。html

在iOS中,UIApplication,NSNotificationCenter,NSFileManager等類都是單例模式的實例。設計模式

在ARC環境下,結合GCD能夠實現單例模式:安全

單例類Singleton類的接口部分:app

  1. #import <Foundation/Foundation.h>  函數

  2.   

  3. @interface  Singleton : NSObject <NSCopying>  性能

  4.   

  5. + (Singleton *)sharedInstance;  測試

  6.   

  7. @property (strongnonatomicNSString *str;  優化

  8.   

  9. - (void)print;  atom

  10.   

  11. @end   spa




其中經過sharedInstance方法能夠獲取該類的單例。

實現部分:

  1. #import "Singleton.h"  

  2.   

  3. @implementation Singleton  

  4.   

  5. __strong static Singleton *singleton = nil;  

  6.   

  7. + (Singleton *)sharedInstance {  

  8.     NSLog(@"調用sharedInstance方法");  

  9.       

  10.     static dispatch_once_t onceToken = 0;  

  11.     dispatch_once(&onceToken, ^{  

  12.         NSLog(@"建立Singleton實例");  

  13.         singleton = [[super allocWithZone:NULL] init];  

  14.         singleton.str = @"Say something";  

  15.     });  

  16.       

  17.     return singleton;  

  18. }  

  19.   

  20. + (id)allocWithZone:(NSZone *)zone {  

  21.     NSLog(@"調用alloc方法");  

  22.     return [self sharedInstance];  

  23. }  

  24.   

  25. - (id)copyWithZone:(NSZone *)zone {  

  26.     NSLog(@"調用copy方法");  

  27.     return self;  

  28. }  

  29.   

  30. - (void)print {  

  31.     NSLog(@"%@"self.str);  

  32. }  

  33.   

  34. @end  




dispatch_once函數確保該類的實例對象只建立一次。

allocWithZone方法和copyWithZone方法確保該類的實例對象不會再次被建立。


關於dispatch_once函數:

使用dispatch_once提價的代碼塊,即使你提交屢次,只能執行一次。 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block); 第一個參數是一個傳出參數用來保存代碼塊在隊列運行時被賦的值,若是你想讓本身的代碼只執行一次的話,你必須指定一個一樣的標識符,其實它是long類型的長整數,即typedef long dispatch_once_t。 第二個參數是一個代碼塊,這個代碼塊沒有參數和返回值。 dispatch_once 中的代碼塊默認的狀況下在當前的線程內中執行(也就是被調用函數所在的線程) 

dispatch_once不只意味着代碼僅會被運行一次,並且仍是線程安全的。

該方法有不少優點: 
1 線程安全
2 很好知足靜態分析器要求
3 和自動引用計數(ARC)兼容 
4 僅須要少許代碼


測試結果:

咱們在程序運行的時候建立一個Singleton對象:

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  

  2. {  

  3.     Singleton *sin = [Singleton sharedInstance];  

  4.     NSLog(@"---------------------");  

  5.       

  6.     return YES;  

  7. }  


在View Controller類屢次使用sharedInstance方法,alloc init方法和copy方法來嘗試建立Singleton類對象:

  1. - (void)viewDidLoad  

  2. {  

  3.     [super viewDidLoad];  

  4.       

  5.     Singleton *singleton = [Singleton sharedInstance];  

  6.     if (singleton) {  

  7.         [singleton print];  

  8.     }  

  9.     NSLog(@"---------------------");  

  10.       

  11.     Singleton *singleton2 = [Singleton sharedInstance];  

  12.     if (singleton2) {  

  13.         [singleton2 print];  

  14.     }  

  15.     NSLog(@"---------------------");  

  16.       

  17.     singleton = nil;  

  18.       

  19.     singleton = [[Singleton alloc] init];  

  20.     if (singleton) {  

  21.         [singleton print];  

  22.     }  

  23.     NSLog(@"---------------------");  

  24.       

  25.     Singleton *singleton3 = [singleton2 copy];  

  26.     if (singleton3) {  

  27.         [singleton3 print];  

  28.     }  

  29. }  


運行結果:

  1. 2014-02-05 21:41:25.323 Singleton[4827:70b] 調用sharedInstance方法  

  2. 2014-02-05 21:41:25.324 Singleton[4827:70b] 建立Singleton實例  

  3. 2014-02-05 21:41:25.324 Singleton[4827:70b] ---------------------  

  4. 2014-02-05 21:41:25.325 Singleton[4827:70b] 調用sharedInstance方法  

  5. 2014-02-05 21:41:25.326 Singleton[4827:70b] Say something  

  6. 2014-02-05 21:41:25.326 Singleton[4827:70b] ---------------------  

  7. 2014-02-05 21:41:25.326 Singleton[4827:70b] 調用sharedInstance方法  

  8. 2014-02-05 21:41:25.327 Singleton[4827:70b] Say something  

  9. 2014-02-05 21:41:25.327 Singleton[4827:70b] ---------------------  

  10. 2014-02-05 21:41:25.327 Singleton[4827:70b] 調用alloc方法  

  11. 2014-02-05 21:41:25.327 Singleton[4827:70b] 調用sharedInstance方法  

  12. 2014-02-05 21:41:25.328 Singleton[4827:70b] Say something  

  13. 2014-02-05 21:41:25.328 Singleton[4827:70b] ---------------------  

  14. 2014-02-05 21:41:25.328 Singleton[4827:70b] 調用copy方法  

  15. 2014-02-05 21:41:25.328 Singleton[4827:70b] Say something  


由控制檯輸出可見,在程序剛剛啓動時經過sharedInstance中的dispatch_once函數建立了一個Singleton實例,隨後的sharedInstance方法或alloc init方法或copy方法都不會致使dispatch_once函數的調用,也就是Singleton實例只被建立了一次。


參考資料:

IOS GCD 使用(三)單例模式

dispatch_once優化代碼性能

IOS dispatch_once

object-c 單例模式(包括ARC)


後記:

dispatch_once函數的原型爲:void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block),其中dispatch_once_t是long類型的typedef,dispatch_block_t是一個代碼塊的typedef。

這個函數的做用是使得block在整個程序的生命週期中只執行一次,每次調用這段代碼時經過predicate來檢查,在這裏predicate必須嚴格地初始化爲0

能夠測試下其輸出:

  1. + (UIColor *)boringColor;  

  2. {  

  3.     static UIColor *color;  

  4.     static dispatch_once_t onceToken;  

  5.     NSLog(@"%ld", onceToken);  

  6.     dispatch_once(&onceToken, ^{  

  7.         NSLog(@"dispatch_once");  

  8.         color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];  

  9.     });  

  10.     NSLog(@"%ld", onceToken);  

  11.     NSLog(@"--------------");  

  12.     return color;  

  13. }  

  14.   

  15. - (void)viewDidLoad  

  16. {  

  17.     [super viewDidLoad];  

  18.       

  19.       

  20.     [Color boringColor];  

  21.     [Color boringColor];  

  22.     [Color boringColor];  

  23. }  


  1. 2014-03-23 14:01:30.653 once_t[1215:60b] 0  

  2. 2014-03-23 14:01:30.655 once_t[1215:60b] dispatch_once  

  3. 2014-03-23 14:01:30.655 once_t[1215:60b] -1  

  4. 2014-03-23 14:01:30.655 once_t[1215:60b] --------------  

  5. 2014-03-23 14:01:30.656 once_t[1215:60b] -1  

  6. 2014-03-23 14:01:30.656 once_t[1215:60b] -1  

  7. 2014-03-23 14:01:30.656 once_t[1215:60b] --------------  

  8. 2014-03-23 14:01:30.656 once_t[1215:60b] -1  

  9. 2014-03-23 14:01:30.657 once_t[1215:60b] -1  

  10. 2014-03-23 14:01:30.657 once_t[1215:60b] --------------  


若onceToken被初始化爲0,那麼在調用dispatch_once函數時檢查到其值爲0,就執行block,執行完畢後onceToken減一。下一次調用dispatch_once函數時檢查到onceToken = -1,將不會執行block。

下面咱們修改一下:

[objc] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. static dispatch_once_t onceToken = 1L;  


控制檯輸出一直停留在1,程序加載不了、內存保存不變,一直卡在dispatch_once函數那裏。

若是將onceToken設置爲-2,和設置爲1的狀況同樣。

若是將onceToken設置爲-1,不會陷入死循環中,可是block沒有被執行,某些變量可能沒有被初始化。

可見隨意修改onceToken的值有多危險了,同時這也是將其屬性設置爲static的緣由,由於要在文件域中一直持有其值。


小結:

調用dispatch_once(predicate, block)時,函數首先檢查predicate:

若是predicate = 0,那麼執行block。

若是predicate = -1,那麼不執行block並繼續往下執行。

若是predicate != 0 或 -1,那麼程序將一直卡在dispatch_once函數那裏。

結論:必定要將predicate初始化爲0,並將其範圍設置爲static,另外不要隨意改變其值(不要將其設置爲類的屬性,實例變量等),不然會致使沒法預見的行爲。

相關文章
相關標籤/搜索