ios開發(fā)基礎(chǔ)學(xué)習(xí)筆記(六)--單例模式

本文轉(zhuǎn)載http://www.cocoachina.com/ios/20171123/21300.html

單例模式大概是設(shè)計模式中最簡單的一個习寸。本來沒什么好說的棺禾,但是實踐過程中還是有一些坑桦山。所以本文小結(jié)一下在iOS開發(fā)中的單例模式螺戳。

一、 什么是單例模式

按照四人幫(GOF)教科書的說法碌燕,標(biāo)準(zhǔn)定義是這樣的:

Ensures a ``class has only one instance, and provide a global point of access to it.

保證一個類只有一個實例误证,并且提供一個全局的訪問入口訪問這個實例。

然后修壕,類圖是這個樣子的:

單例類圖.png

什么時候選擇單例模式呢愈捅?

一個類必須只有一個對象〈瑞客戶端必須通過一個眾所周知的入口訪問這個對象蓝谨。

這個唯一的對象需要擴展的時候,只能通過子類化的方式青团∑┪祝客戶端的代碼能夠不需要任何修改就能夠使用擴展后的對象。

上面的官方說法壶冒,聽起來一頭霧水缕题。我的理解是這樣的。

在建模的時候胖腾,如果這個東西確實只需要一個對象,多余的對象都是無意義的瘪松,那么就考慮用單例模式咸作。比如定位管理(CLLocationManager),硬件設(shè)備就只有一個宵睦,弄再多的邏輯對象意義不大记罚。所以就會考慮用單例

二、 如何實現(xiàn)基本的單例模式壳嚎?

那么桐智,我們就用Objective-C來實現(xiàn)一下單例模式吧。

要實現(xiàn)比較好的訪問烟馅,我們就會想到用工廠方法創(chuàng)建對象说庭,提供統(tǒng)一的創(chuàng)建方法的地方給外部使用。要實現(xiàn)僅有一個對象郑趁,就會想到用一個全局的東西保存這個對象刊驴,然后在創(chuàng)建對象的工廠方法中判斷一下,如果對象存在,那么就返回該對象捆憎。如果不存在舅柜,就造一個返回出去。

于是躲惰,基本的單例實現(xiàn)就這樣了:

DJSingleton * g_instance_dj_singleton = nil ;
+ (DJSingleton *)shareInstance{
    if(g_instance_dj_singleton == nil) {
        g_instance_dj_singleton = [[DJSingleton alloc] init];
    }
    return` `(DJSingleton *)g_instance_dj_singleton;
}

看起來不錯致份。不過這個全局的變量 g_instance_dj_singleton有個缺點,就是外面的人隨便可以改础拨,為了隔離外部修改氮块,可以設(shè)置成靜態(tài)變量,就是這樣子:

+ (DJSingleton *)shareInstance{
    static DJSingleton * s_instance_dj_singleton = nil ;
    if (s_instance_dj_singleton == nil) {
        s_instance_dj_singleton = [[DJSingleton alloc] init];
    } 
    return (DJSingleton *)s_instance_dj_singleton;
}

單例的核心思想算是實現(xiàn)了太伊。

三雇锡、 多線程怎么辦?

雖然核心思想實現(xiàn)了僚焦,但是依舊不完美锰提。考慮下多線程的情況芳悲。即多個線程同時訪問這個工廠方法立肘,能夠總是保證只創(chuàng)建一個實例對象么?

顯然上面的方式是有問題的名扛。比如第一個線程執(zhí)行到第4行但是還沒有進(jìn)行賦值操作谅年,第二個線程執(zhí)行第三行。此時判斷對象依舊為nil肮韧,第二個線程也能往下執(zhí)行到創(chuàng)建對象操作的第4行融蹂。從而創(chuàng)建了多個對象。

那么弄企,如何保證多線程下依舊能夠只創(chuàng)建一個呢超燃?這里面的核心思路,是要保證s_instance_dj_singleton這個臨界資源的訪問(讀取和賦值)拘领。

iOS下控制多線程的方式有很多意乓,可以使用NSLock,可以@synchronized等各種線程同步的技術(shù)约素。于是届良,我們的單例代碼變成了這樣:

+ (DJSingleton *)shareInstance{
    static DJSingleton * s_instance_dj_singleton = nil ;
    @synchronized(self) {
         if (s_instance_dj_singleton == nil) {
         s_instance_dj_singleton = [[DJSingleton alloc] init];
         }
     }
     return (DJSingleton *)s_instance_dj_singleton;
}

看起來多線程沒啥問題了了。不過我們可以做的更好圣猎。OC的內(nèi)部機制里有一種更加高效的方式士葫,那就是dispatch_once。性能相差好幾倍样漆,好幾十倍为障。關(guān)于性能的比對,大神們做過實驗和分析。請參考這里鳍怨。

于是呻右,我們的單例變成了這個樣子:

+ (DJSingleton *)shareInstance{
    static DJSingleton * s_instance_dj_singleton = nil ;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (s_instance_dj_manager == nil) {
        s_instance_dj_manager = [[DJSingleton alloc] init];
        }
    });
    return (DJSingleton *)s_instance_dj_singleton;
}

四、Objective-C的坑

看起來很完美了鞋喇∩模可是Objective-C畢竟是Objective-C。別的語言侦香,諸如C++落塑,java,構(gòu)造方法可以隱藏罐韩。Objective-C中的方法憾赁,實際上都是公開的,雖然我們提供了一個方便的工廠方法的訪問入口散吵,但是里面的alloc方法依舊是可見的龙考,可以調(diào)用到的。也就是說矾睦,雖然你給了我一個工廠方法晦款,調(diào)皮的小伙伴可能依舊會使用alloc的方式創(chuàng)建對象。這樣會導(dǎo)致外面使用的時候枚冗,依舊可能創(chuàng)建多個實例缓溅。

關(guān)于這個事情的處理,可以分為兩派赁温。一個是冷酷派坛怪,技術(shù)上實現(xiàn)無論你怎么調(diào)用,我都給你同一個單例對象股囊;一個是溫柔派酝陈,是從編譯器上給調(diào)皮的小伙伴提示,你不能這么造對象毁涉,溫柔的指出有問題,但不強制約束锈死。

1. 冷酷派的實現(xiàn)

冷酷派的實現(xiàn)從OC的對象創(chuàng)建角度出發(fā)贫堰,就是把創(chuàng)建對象的各種入口給封死了。alloc待牵,copy等等其屏,無論是采用哪種方式創(chuàng)建,我都保證給出的對象是同一個缨该。

由Objective-C的一些特性可以知道偎行,在對象創(chuàng)建的時候,無論是alloc還是new,都會調(diào)用到 allocWithZone方法蛤袒。在通過拷貝的時候創(chuàng)建對象時熄云,會調(diào)用到-(id)copyWithZone:(NSZone *)zone,-(id)mutableCopyWithZone:(NSZone *)zone方法妙真。因此缴允,可以重寫這些方法,讓創(chuàng)建的對象唯一珍德。

+(id)allocWithZone:(NSZone *)zone{
    return [DJSingleton sharedInstance];
}
+(DJSingleton *) sharedInstance{
    static DJSingleton * s_instance_dj_singleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        s_instance_dj_singleton = [[super allocWithZone:nil] init];
    });
    return s_instance_dj_singleton;
}
-(id)copyWithZone:(NSZone *)zone{
    return [DJSingleton sharedInstance];
}
-(id)mutableCopyWithZone:(NSZone *)zone{
    return [DJSingleton sharedInstance];
}

2. 溫柔派的實現(xiàn)

溫柔派就直接告訴外面练般,alloc,new锈候,copy薄料,mutableCopy方法不可以直接調(diào)用。否則編譯不過泵琳。

+(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")));

我個人的話比較喜歡采用溫柔派的實現(xiàn)摄职。不需要這么多復(fù)雜的實現(xiàn)。也讓使用方有比較明確的概念這個是個單例虑稼,不要調(diào)皮琳钉。對于一般的業(yè)務(wù)場景是足夠了的。

五蛛倦、 單例模式潛在的問題

1. 內(nèi)存問題

單例模式實際上延長了對象的生命周期歌懒。那么就存在內(nèi)存問題。因為這個對象在程序的整個生命都存在溯壶。所以當(dāng)這個單例比較大的時候及皂,總是hold住那么多內(nèi)存,就需要考慮這件事了且改。另外验烧,可能單例本身并不大,但是它如果強引用了另外的比較大的對象又跛,也算是一個問題碍拆。別的對象因為單例對象不釋放而不釋放。

當(dāng)然這個問題也有一定的辦法慨蓝。比如對于一些可以重新加載的對象感混,在需要的時候加載,用完之后礼烈,單例對象就不再強引用弧满,從而把原先hold住的對象釋放掉。下次需要再加載回來此熬。

2. 循環(huán)依賴問題

在開發(fā)過程中庭呜,單例對象可能有一些屬性滑进,一般會放在init的時候創(chuàng)建和初始化。這樣募谎,比如如果單例A的m屬性依賴于單例B扶关,單例B的屬性n依賴于單例A,初始化的時候就會出現(xiàn)死循環(huán)依賴近哟。死在dispatch_once里驮审。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吉执,隨后出現(xiàn)的幾起案子疯淫,更是在濱河造成了極大的恐慌,老刑警劉巖戳玫,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熙掺,死亡現(xiàn)場離奇詭異,居然都是意外死亡咕宿,警方通過查閱死者的電腦和手機币绩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來府阀,“玉大人缆镣,你說我怎么就攤上這事∈哉悖” “怎么了董瞻?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長田巴。 經(jīng)常有香客問我钠糊,道長,這世上最難降的妖魔是什么壹哺? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任抄伍,我火速辦了婚禮,結(jié)果婚禮上管宵,老公的妹妹穿的比我還像新娘截珍。我一直安慰自己,他們只是感情好箩朴,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布笛臣。 她就那樣靜靜地躺著,像睡著了一般隧饼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上静陈,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天燕雁,我揣著相機與錄音诞丽,去河邊找鬼。 笑死拐格,一個胖子當(dāng)著我的面吹牛僧免,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捏浊,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懂衩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了金踪?” 一聲冷哼從身側(cè)響起浊洞,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胡岔,沒想到半個月后法希,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡靶瘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年苫亦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怨咪。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屋剑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诗眨,到底是詐尸還是另有隱情唉匾,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布辽话,位于F島的核電站肄鸽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油啤。R本人自食惡果不足惜典徘,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望益咬。 院中可真熱鬧逮诲,春花似錦、人聲如沸幽告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗锁。三九已至齐唆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冻河,已是汗流浹背箍邮。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工茉帅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锭弊。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓堪澎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親味滞。 傳聞我的和親對象是個殘疾皇子樱蛤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容