單例模式,是一種常用的軟件設(shè)計模式。在應(yīng)用這個模式時,單例對象的類 必須保證只有一個實(shí)例存在。 許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。 比如在某個服務(wù)器程序中,該服務(wù)器的配置信息存放在一個文件中,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。
實(shí)現(xiàn)單例模式的思路是:一個類能返回對象一個引用(永遠(yuǎn)是同一個)和一個獲得該實(shí)例的方法(必須是靜態(tài)方法,通常使用sharedInstance這個名稱);當(dāng)我們調(diào)用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引用;同時我們還將該類的構(gòu)造函數(shù)定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造函數(shù)來實(shí)例化該類的對象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實(shí)例。
單例模式在多線程的應(yīng)用場合下必須小心使用。如果當(dāng)唯一實(shí)例尚未創(chuàng)建時,有兩個線程同時調(diào)用創(chuàng)建方法,那么它們同時沒有檢測到唯一實(shí)例的存在,從而同時各自創(chuàng)建了一個實(shí)例,這樣就有兩個實(shí)例被構(gòu)造出來,從而違反了單例模式中實(shí)例唯一的原則。 解決這個問題的辦法是為指示類是否已經(jīng)實(shí)例化的變量提供一個互斥鎖(雖然這樣會降低效率)。
蘋果官方示例中如下定義單例
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是因?yàn)楸WC分配對象的唯一性
原因是單例類只有一個唯一的實(shí)例,而平時我們在初始化一個對象的時候, [[Class alloc] init],其實(shí)是做了兩件事。 alloc 給對象分配內(nèi)存空間,init是對對象的初始化,包括設(shè)置成員變量初值這些工作。而給對象分配空間,除了alloc方法之外,還有另一個方法: allocWithZone.
而實(shí)踐證明,使用alloc方法初始化一個類的實(shí)例的時候,默認(rèn)是調(diào)用了 allocWithZone 的方法。于是覆蓋allocWithZone方法的原因已經(jīng)很明顯了:為了保持單例類實(shí)例的唯一性,需要覆蓋所有會生成新的實(shí)例的方法,如果有人初始化這個單例類的時候不走[[Class alloc] init] ,而是直接 allocWithZone, 所以我們應(yīng)該怎么使用單例呢?
+ (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 主要是因?yàn)闉榱思渔i保證單例的唯一性
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ā)現(xiàn),在 dispatch_once 執(zhí)行前,onceToken 的值是 0,因?yàn)?dispatch_once_t 是由 typedef long dispatch_once_t 而來,所以在 onceToken 還沒被手動賦值的情況下,0 是編譯器給 onceToken 的初始化賦值。
在 dispatch_once 執(zhí)行過程中,onceToken 是一個很大的數(shù)字,這個值是 dispath_once 內(nèi)部實(shí)現(xiàn)中一個局部變量的地址,并不是一個固定的值。
當(dāng) dispatch_once 執(zhí)行完畢,onceToken 的值被賦為 -1。之后再次調(diào)用的時候,onceToken已經(jīng)是-1了,就直接跳過dispatch_once的執(zhí)行
dispatch_once 第一次執(zhí)行,block 被調(diào)用,調(diào)用結(jié)束需標(biāo)記 onceToken。
dispatch_once 是同步阻塞線程,第一次執(zhí)行過程中,有其它線程的請求需要等待 dispatch_once 的第一次執(zhí)行結(jié)束才能被處理。
dispatch_once 同步線程執(zhí)行完畢onceToken會修改值為-1,有其它線程執(zhí)行該 dispatch_once,則直接跳過 block 執(zhí)行后續(xù)任務(wù)。
上面是通過覆蓋其他初始化方法來保證分配對象的唯一性
當(dāng)然我們也可以直接禁止直接禁止掉其他的初始化方法
+ (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")));
這樣如果外部調(diào)用單例的其他初始化方法就會直接報錯
為了避免創(chuàng)建單例的時候?qū)懱嘀貜?fù)代碼,我們可以通過以下兩種宏定義來創(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;\
}