淺談iOS設計模式之單例模式
iOS中單例模式很常見,比如Cocoa中的一些對象方法, [UIColor redColor] 等等。
顧名思義,單例模式確保了一個類只有一個實例。
一個常見的寫法如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { instance = [[superalloc] init]; } returninstance; }
這種寫法的優點是,可以延遲加載,按需分配內存以節省開銷。
但是,這并非一個線程安全的寫法,比如兩個或多個線程并發的調用 sharedInstance 方法,有可能會得到多個實例,這里有幾種方法來創建一個線程安全的單例。
@synchronized
可以使用@synchronized進行加鎖,代碼如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { @synchronized(self) { if(!instance) { instance = [[superalloc] init]; } } returninstance; }
這種寫法也是懶加載,不過雖然保證了線程安全但是由于鎖的存在當多線程訪問時,性能會降低。
雙重檢查鎖
雙重檢查鎖,主要是為了避免對除第一次調用外的所有調用都實行同步的昂貴代價。
就是并不是每次進入 sharedInstance 方法都需要同步,而是先不同步,進入方法過后,先檢查實例是否存在,如果不存在才進入下面的同步塊,這是第一重檢查。
進入同步塊過后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。
如此,就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。
代碼如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { @synchronized(self) { if(!instance) { instance = [[superalloc] init]; } } } returninstance; }
這段代碼乍一看,似乎沒什么問題,問題是CPU和編譯器可能會對內存訪問指令進行重新排序。參考了一下 Care and Feeding of Singletons 這篇文章中的方法。可以在訪問變量前插入 barriers 并且使用 volatile 關鍵字。內存 barriers 在 libkern/OSAtomic.h 頭文件中,這用來解決CPU的問題, vilatile 關鍵字用來解決編譯器的問題。代碼如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *volatileinstance =nil; @implementationSingleton + (Singleton *)sharedInstance { if(!instance) { @synchronized(self) { if(!instance) { OSMemoryBarrier(); instance = [[superalloc] init]; } } } OSMemoryBarrier(); returninstance; }
雖然相較于上一種方法減少了部分鎖的開銷,但是這個開銷依然不小。而且實現方式本身頗具爭議且復雜。
GCD
這里主要利用GCD中的 dispatch_once 方法,這是最普遍也是蘋果最推薦的方法,函數原型如下:
voiddispatch_once( dispatch_once_t*predicate, dispatch_block_t block);
單例實現代碼如下:
/* Singleton.h */ #import <Foundation/Foundation.h> @interfaceSingleton:NSObject + (Singleton *)sharedInstance; @end /* Singleton.m */ #import "Singleton.h" staticSingleton *instance =nil; @implementationSingleton + (Singleton *)sharedInstance { staticdispatch_once_tpredicate; dispatch_once(&predicate, ^{ instance = [[InstanceClass alloc] init]; }); returninstance; }
這樣的方法有很多優勢,首先滿足了線程安全問題,其次很好滿足靜態分析器要求。
GCD可以確保以更快的方式完成這些檢測,它可以保證 block 中的代碼在任何線程通過 dispatch_once 調用之前被執行,但它不會強制每次調用這個函數都讓代碼進行同步控制。
蘋果的文檔 documentation for dispatch_once 是這么說的:
The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage (including Objective-C instance variables) is undefined.
所以,如果你的predicate不是靜態的、不是全局的,還是不能用GCD。其實如果你去看這個函數所在的頭文件,你會發現目前它的實現其實是一個宏。