單例模式完整寫法及原理

單例模式,是一種常用的軟件設(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;\
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 單例模式 什么是單例模式? 單例模式想一個大獨(dú)裁者,他規(guī)定在他的國度里面,所有數(shù)據(jù)的訪問和請求都得經(jīng)過他,甚至你要...
    GitHubPorter閱讀 1,251評論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,626評論 1 32
  • 什么是單例模式? >是開發(fā)設(shè)計模式(共23種)中的1種 >它可以保證在程序運(yùn)行過程,一個類只有一個實(shí)例(一個對象)...
    泥孩兒0107閱讀 284評論 0 0
  • 1.設(shè)計模式是什么? 你知道哪些設(shè)計模式,并簡要敘述?設(shè)計模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,294評論 0 12
  • 奶奶雙手扒在車窗前,有些戀戀不舍,又有點(diǎn)可憐巴巴的對我說:“我湯都燒好啦,就準(zhǔn)備你來喝呢?!?我對奶奶笑了笑,說:...
    anniean閱讀 506評論 0 0

友情鏈接更多精彩內(nèi)容