單例模式斥季,是一種常用的軟件設計模式。在應用這個模式時参萄,單例對象的類 必須保證只有一個實例存在卫枝。 許多時候整個系統只需要擁有一個的全局對象,這樣有利于我們協調系統整體的行為讹挎。 比如在某個服務器程序中校赤,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取筒溃,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息马篮。這種方式簡化了在復雜環(huán)境下的配置管理。
實現單例模式的思路是:一個類能返回對象一個引用(永遠是同一個)和一個獲得該實例的方法(必須是靜態(tài)方法怜奖,通常使用sharedInstance這個名稱)浑测;當我們調用這個方法時,如果類持有的引用不為空就返回這個引用歪玲,如果類保持的引用為空就創(chuàng)建該類的實例并將實例的引用賦予該類保持的引用迁央;同時我們還將該類的構造函數定義為私有方法,這樣其他處的代碼就無法通過調用該類的構造函數來實例化該類的對象滥崩,只有通過該類提供的靜態(tài)方法來得到該類的唯一實例岖圈。
單例模式在多線程的應用場合下必須小心使用。如果當唯一實例尚未創(chuàng)建時钙皮,有兩個線程同時調用創(chuàng)建方法蜂科,那么它們同時沒有檢測到唯一實例的存在,從而同時各自創(chuàng)建了一個實例株灸,這樣就有兩個實例被構造出來崇摄,從而違反了單例模式中實例唯一的原則。 解決這個問題的辦法是為指示類是否已經實例化的變量提供一個互斥鎖(雖然這樣會降低效率)慌烧。
蘋果官方示例中如下定義單例
static MyGizmoClass *sharedGizmoManager = nil;
+ (MyGizmoClass*)sharedManager
{
if (sharedGizmoManager == nil) {
sharedGizmoManager = [[super allocWithZone:NULL] init];
}
return sharedGizmoManager;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedManager] retain];
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (NSUInteger)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
使用allocWithZone是因為保證分配對象的唯一性
原因是單例類只有一個唯一的實例逐抑,而平時我們在初始化一個對象的時候, [[Class alloc] init]屹蚊,其實是做了兩件事厕氨。 alloc 給對象分配內存空間,init是對對象的初始化汹粤,包括設置成員變量初值這些工作命斧。而給對象分配空間,除了alloc方法之外嘱兼,還有另一個方法: allocWithZone.
而實踐證明国葬,使用alloc方法初始化一個類的實例的時候,默認是調用了 allocWithZone 的方法。于是覆蓋allocWithZone方法的原因已經很明顯了:為了保持單例類實例的唯一性汇四,需要覆蓋所有會生成新的實例的方法接奈,如果有人初始化這個單例類的時候不走[[Class alloc] init] ,而是直接 allocWithZone通孽, 所以我們應該怎么使用單例呢序宦?
+ (instancetype)shareInstance
{
static LLSingleton *shareInstace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstace = [[super allocWithZone:nil] init];
});
return shareInstace;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
return [LLSingleton shareInstance];
}
- (id)copyWithZone:(NSZone *)zone
{
return [LLSingleton shareInstance];
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
return [LLSingleton shareInstance];
}
我們之所以使用dispatch_once 主要是因為為了加鎖保證單例的唯一性
2019-07-15 14:09:22.051730+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = 0
2019-07-15 14:09:22.051841+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = 768
2019-07-15 14:09:22.051919+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
2019-07-15 14:09:22.051986+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
2019-07-15 14:09:22.052056+0800 ShareInceTanceDemo[21216:549538] before dispatch_once onceToken = -1
通過輸出我們可以發(fā)現,在 dispatch_once 執(zhí)行前背苦,onceToken 的值是 0互捌,因為 dispatch_once_t 是由 typedef long dispatch_once_t 而來,所以在 onceToken 還沒被手動賦值的情況下行剂,0 是編譯器給 onceToken 的初始化賦值秕噪。
在 dispatch_once 執(zhí)行過程中,onceToken 是一個很大的數字硼讽,這個值是 dispath_once 內部實現中一個局部變量的地址巢价,并不是一個固定的值。
當 dispatch_once 執(zhí)行完畢固阁,onceToken 的值被賦為 -1壤躲。之后再次調用的時候,onceToken已經是-1了备燃,就直接跳過dispatch_once的執(zhí)行
dispatch_once 第一次執(zhí)行碉克,block 被調用,調用結束需標記 onceToken并齐。
dispatch_once 是同步阻塞線程漏麦,第一次執(zhí)行過程中,有其它線程的請求需要等待 dispatch_once 的第一次執(zhí)行結束才能被處理况褪。
dispatch_once 同步線程執(zhí)行完畢onceToken會修改值為-1撕贞,有其它線程執(zhí)行該 dispatch_once,則直接跳過 block 執(zhí)行后續(xù)任務测垛。
上面是通過覆蓋其他初始化方法來保證分配對象的唯一性
當然我們也可以直接禁止直接禁止掉其他的初始化方法
+ (instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));
+ (instancetype) new __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
這樣如果外部調用單例的其他初始化方法就會直接報錯
為了避免創(chuàng)建單例的時候寫太多重復代碼捏膨,我們可以通過以下兩種宏定義來創(chuàng)建單例
一、直接覆蓋其他初始化方法
// .h文件
#define SingletonH(name) + (instancetype)shared##name;
// .m文件
#define SingletonM(name) \
static id _instace; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [[self alloc] init]; \
}); \
return _instace; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instace; \
}
二食侮、禁止掉其他初始化方法
// .h文件
#define SINGLETON_H(_type_) + (_type_ *)sharedInstance;\
+(instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));\
+(instancetype) new __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead")));\
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));\
// .m文件
#define SINGLETON_M(_type_) + (_type_ *)sharedInstance{\
static _type_ *_instace = nil;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instace = [[super alloc] init];\
});\
return _instace;\
}