單例模式完整寫法及原理

單例模式斥季,是一種常用的軟件設計模式。在應用這個模式時参萄,單例對象的類 必須保證只有一個實例存在卫枝。 許多時候整個系統只需要擁有一個的全局對象,這樣有利于我們協調系統整體的行為讹挎。 比如在某個服務器程序中校赤,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取筒溃,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息马篮。這種方式簡化了在復雜環(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;\
}

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末号涯,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子锯七,更是在濱河造成了極大的恐慌链快,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眉尸,死亡現場離奇詭異域蜗,居然都是意外死亡巨双,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門地消,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炉峰,“玉大人,你說我怎么就攤上這事脉执。” “怎么了戒劫?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵半夷,是天一觀的道長。 經常有香客問我迅细,道長巫橄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任茵典,我火速辦了婚禮湘换,結果婚禮上,老公的妹妹穿的比我還像新娘统阿。我一直安慰自己彩倚,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布扶平。 她就那樣靜靜地躺著帆离,像睡著了一般。 火紅的嫁衣襯著肌膚如雪结澄。 梳的紋絲不亂的頭發(fā)上哥谷,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音麻献,去河邊找鬼们妥。 笑死,一個胖子當著我的面吹牛勉吻,可吹牛的內容都是我干的监婶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼餐曼,長吁一口氣:“原來是場噩夢啊……” “哼压储!你這毒婦竟也來了?” 一聲冷哼從身側響起源譬,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤集惋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后踩娘,有當地人在樹林里發(fā)現了一具尸體刮刑,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡喉祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了雷绢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泛烙。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翘紊,靈堂內的尸體忽然破棺而出蔽氨,到底是詐尸還是另有隱情,我是刑警寧澤帆疟,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布鹉究,位于F島的核電站,受9級特大地震影響踪宠,放射性物質發(fā)生泄漏自赔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一柳琢、第九天 我趴在偏房一處隱蔽的房頂上張望绍妨。 院中可真熱鬧,春花似錦柬脸、人聲如沸他去。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孤页。三九已至,卻和暖如春涩馆,著一層夾襖步出監(jiān)牢的瞬間行施,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工魂那, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛾号,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓涯雅,卻偏偏與公主長得像鲜结,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子活逆,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容

  • 單例模式 什么是單例模式精刷? 單例模式想一個大獨裁者,他規(guī)定在他的國度里面蔗候,所有數據的訪問和請求都得經過他怒允,甚至你要...
    GitHubPorter閱讀 1,161評論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,105評論 1 32
  • 什么是單例模式? >是開發(fā)設計模式(共23種)中的1種 >它可以保證在程序運行過程锈遥,一個類只有一個實例(一個對象)...
    泥孩兒0107閱讀 248評論 0 0
  • 1.設計模式是什么? 你知道哪些設計模式丽惶,并簡要敘述炫七?設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,158評論 0 12
  • 奶奶雙手扒在車窗前,有些戀戀不舍抡秆,又有點可憐巴巴的對我說:“我湯都燒好啦壤圃,就準備你來喝呢±旁” 我對奶奶笑了笑,說:...
    anniean閱讀 424評論 0 0