objc文章之避免濫用單例

這是一篇objc上的文章躺坟,解了我很多的關(guān)于單例的困惑阅懦,原文地址 中文翻譯地址

1.單例介紹

單例是Cocoa中被廣泛使用的設(shè)計模式之一蝗砾, 比如系統(tǒng)的UIApplication和NSFileManager等汇荐。

+(instancetype)sharedInstance{
          static dispatch_once_t once;
          static id sharedInstance;
          dispatch_once(&once,^{
                sharedInstance = [[self alloc]init];
          });
          return sharedInstance;
}

單例的含義是:一個應(yīng)用程序中日熬,只有一個該類的實例棍厌。它在第一次被訪問的時候創(chuàng)建,在應(yīng)用結(jié)束的時候結(jié)束竖席。

2. 單例的問題1:全局狀態(tài)

大多數(shù)人認(rèn)為全局狀態(tài)是不好的行為耘纱,太多的狀態(tài)難以理解,難以調(diào)試毕荐。
首先我們看一個例子1

@implementation SPMath
{
NSInteger _a;
NSInteger _b;
}

-(NSInteger)computeSum{
return  _a + _b;
}

//例子1 有兩個問題:

  • computeSum函數(shù)沒有顯式的聲明它依賴 _a 和 _b 束析。 我們需要看這個函數(shù)具體的實現(xiàn)才能明白這個函數(shù)依賴那些變量。 隱藏依賴不好东跪。
  • 當(dāng)為調(diào)用computeSum做準(zhǔn)備而修改_a 和 _b的數(shù)值的時候畸陡,我們需要保證這些修改不會影響其他依賴于這兩個變量的代碼的正確性鹰溜,而這在多線程環(huán)境是尤其困難的。

下邊是例子2:

+(NSInteger)computeSumOf:(NSInteger)a plus:(NSInteger)b{
return a+b
}

例子2中丁恭,我們顯式的聲明了依賴曹动,我們不需要為了調(diào)用這個方法而去改變實例變量的狀態(tài)。

這個例子和單例的關(guān)系: 單例是全局狀態(tài)牲览,它可以被使用在任何地方墓陈,而不需要顯式的聲明依賴。我們在代碼的任何地方都可以調(diào)用[Singleton sharedInstance]來和這個單例交互第献,同時它也會影響到程序其他用到該單例地方的代碼贡必。

例子3:單例的全局狀態(tài)對其他地方代碼的影響

@interface SPSingleton : NSObject
+ (instancetype)sharedInstance;

- (NSUInteger)badMutableState;
- (void)setBadMutableState:(NSUInteger)badMutableState;
@end

@implementation SPConsumerA
- (void)someMethod
{
    if ([[SPSingleton sharedInstance] badMutableState] ==0 ) {
        //do somethingA
    }else{
       //do somethingB
    }
}
@end

@implementation SPConsumerB
- (void)someOtherMethod
{
    [[SPSingleton sharedInstance] setBadMutableState:0];
}
@end

// 上邊的例子中,SPConsumerA和SPConsumerB是兩個完全獨(dú)立的模塊庸毫,但是呢仔拟,SPConsumerB卻可以通過使用單例,來影響到SPConsumerA的行為,這種情況只能發(fā)生在B模塊顯式的引用A模塊來表明二者之間關(guān)系飒赃。 但是這里送單例利花,導(dǎo)致隱式地兩個不相關(guān)的模塊建立了耦合。

例子4(因為我對測試這塊沒掌握好载佳,這里不是很了解)

@interface SPURLCache

+ (SPCache *)sharedURLCache;

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;

@end 

我們寫了一個網(wǎng)頁查看器炒事,并構(gòu)建了一個URLCache。然后我們寫了3個測試用例蔫慧,沒有網(wǎng)絡(luò)的測試用例挠乳,失敗的測試用例,成功的測試用例姑躲。 過一段時間睡扬,當(dāng)有人改變了測試用例執(zhí)行順序的時候,發(fā)現(xiàn)測試用例不能正常運(yùn)行了肋联。
處理成功的那個測試用例首先被運(yùn)行威蕉,然后再運(yùn)行其他兩個。處理錯誤的那兩個測試用例現(xiàn)在竟然成功了橄仍,和預(yù)期不一樣韧涨,因為 URL cache 這個單例把不同測試用例之間的 response 緩存起來了。
// 這里的結(jié)論是: 持久化的狀態(tài)是單元測試的敵人侮繁。因為單元測試在各個測試用例相互獨(dú)立的情況下才有效虑粥。如果狀態(tài)從一個用例傳遞到另一個,這樣就和測試用例的執(zhí)行順序有關(guān)系了宪哩。

3. 單例的問題2: 對象的生命周期娩贷。

當(dāng)我們在程序中添加一個單例時,很容易認(rèn)為锁孟,永遠(yuǎn)只會有一個實例彬祖。但是茁瘦,在很多iOS代碼中,這種假定很可能被打破储笑。下面是一個例子:當(dāng)單例的生命周期不是整個應(yīng)用的時候甜熔,使用單例會不恰當(dāng)。

//我們有一個應(yīng)用程序突倍,應(yīng)用的用戶可以看好友列表腔稀。有一個thumbnailCache單例,來在設(shè)備上緩存這些好友的圖片信息羽历。

例子5:

 //使用dispatch_once創(chuàng)建單例
@interface SPThumbnailCache : NSObject
+ (instancetype)sharedThumbnailCache;
- (void)cacheProfileImage:(NSData *)imageData forUserId:(NSString *)userId;
@end

//考慮這樣一種情形焊虏,當(dāng)我們有一天實現(xiàn)注銷功能,我們退出登錄用戶1秕磷,然后使用新的賬號用戶2登錄诵闭。這時候我們需要清除之前緩存的信息。如果此時跳夭,單例在子線程執(zhí)行緩存圖片任務(wù)涂圆,那么因為單例一直存在们镜,我們無法取消單例繼續(xù)用戶1的好友圖片币叹。 而此時,我們登錄的是用戶2模狭。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [[SPThumbnailCache sharedThumbnailCache] cacheProfileImage:newImage forUserId:userId];
});

結(jié)論:單例應(yīng)該只用來保存全局的狀態(tài)颈抚,并且不能和任何作用域綁定。如果這些狀態(tài)的作用域比一個完整的應(yīng)用程序的生命周期要短嚼鹉,那么這個狀態(tài)就不應(yīng)該使用單例來管理贩汉。用一個單例來管理用戶綁定的狀態(tài),是不恰當(dāng)?shù)脑O(shè)計模式锚赤。

4. 如何避免使用單例:

依賴注入:我們可以顯式的把對象作為參數(shù)傳遞給依賴對象匹舞。這種技術(shù)叫做依賴注入。 通過依賴注入的方式线脚,避免使用單例赐稽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浑侥,隨后出現(xiàn)的幾起案子姊舵,更是在濱河造成了極大的恐慌,老刑警劉巖寓落,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件括丁,死亡現(xiàn)場離奇詭異,居然都是意外死亡伶选,警方通過查閱死者的電腦和手機(jī)史飞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門尖昏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人构资,你說我怎么就攤上這事会宪。” “怎么了蚯窥?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵掸鹅,是天一觀的道長。 經(jīng)常有香客問我拦赠,道長巍沙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任荷鼠,我火速辦了婚禮句携,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘允乐。我一直安慰自己矮嫉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布牍疏。 她就那樣靜靜地躺著蠢笋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳞陨。 梳的紋絲不亂的頭發(fā)上昨寞,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音厦滤,去河邊找鬼援岩。 笑死,一個胖子當(dāng)著我的面吹牛掏导,可吹牛的內(nèi)容都是我干的享怀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼趟咆,長吁一口氣:“原來是場噩夢啊……” “哼添瓷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忍啸,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仰坦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后计雌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悄晃,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妈橄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庶近。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖眷蚓,靈堂內(nèi)的尸體忽然破棺而出鼻种,到底是詐尸還是另有隱情,我是刑警寧澤沙热,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布叉钥,位于F島的核電站,受9級特大地震影響篙贸,放射性物質(zhì)發(fā)生泄漏投队。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一爵川、第九天 我趴在偏房一處隱蔽的房頂上張望敷鸦。 院中可真熱鬧,春花似錦寝贡、人聲如沸扒披。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碟案。三九已至,卻和暖如春洞焙,著一層夾襖步出監(jiān)牢的瞬間蟆淀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工澡匪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人褒链。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓唁情,卻偏偏與公主長得像,于是被迫代替她去往敵國和親甫匹。 傳聞我的和親對象是個殘疾皇子甸鸟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)兵迅,斷路器抢韭,智...
    卡卡羅2017閱讀 134,664評論 18 139
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡單、最易理解的設(shè)計模式恍箭,也因為它的簡潔易懂刻恭,是項目中最...
    成熱了閱讀 4,254評論 4 34
  • 眾所周知,華為的團(tuán)隊精神崇尚“狼性”文化,因為再強(qiáng)大的動物鳍贾,也難以招架狼群的攻擊鞍匾。因此,華為團(tuán)隊精神的核心就是團(tuán)結(jié)...
    聞方培訓(xùn)師閱讀 2,359評論 0 11
  • 踏著光骑科, 我看見山霧破曉 微風(fēng)橡淑, 朝陽, 小迷茫 穿過夢咆爽, 我聽見聒噪質(zhì)疑 憤怒梁棠, 無奈, 小落寞 雨中的旋律 陌...
    樹上默閱讀 200評論 0 0
  • 畢淑敏是中國最神奇的作家之一,除了文學(xué)成就蜜笤,她在心理咨詢方面也頗有建樹濒蒋。她同時擁有醫(yī)生、作家和心理咨詢師三重身份把兔,...
    虛度時光讀吧閱讀 1,844評論 0 2