iOS單例

iOS 單例

一、單例介紹

單例:該類在程序運(yùn)行期間有且僅有一個(gè)實(shí)例原朝。

1.1 單例模式的要點(diǎn)#

  1. 該類有且只有一個(gè)實(shí)例;
  2. 該類必須能夠自行創(chuàng)建這個(gè)實(shí)例治笨;
  3. 該類必須能夠自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例易迹。

1.2 單例的主要優(yōu)點(diǎn)#

  1. 單例可以保證系統(tǒng)中該類有且僅有一個(gè)實(shí)例,確保所有對(duì)象都訪問這個(gè)唯一實(shí)例幅狮;
  2. 因?yàn)轭惪刂屏藢?shí)例化過程,所以類可以靈活更改實(shí)例化過程株灸;
  3. 基于第 1 條崇摄,對(duì)于項(xiàng)目中的個(gè)別場景的傳值、存儲(chǔ)狀態(tài)等業(yè)務(wù)更加方便慌烧;
  4. 可以節(jié)約系統(tǒng)資源逐抑,對(duì)于一些需要頻繁創(chuàng)建和銷毀的對(duì)象單例模式無疑可以提高系統(tǒng)的性能。

1.3 單例的主要缺點(diǎn)#

  1. 由于單利模式中沒有抽象層杏死,因此單例類的擴(kuò)展有很大的困難泵肄。單例不能被繼承,不能有子類淑翼;
  2. 不易被重寫或擴(kuò)展(可以使用分類)
  3. 單例實(shí)例一旦創(chuàng)建腐巢,對(duì)象指針是保存在靜態(tài)區(qū),那么在堆區(qū)分配的空間只有在應(yīng)用程序終止后才會(huì)被釋放玄括;
  4. 單例類的職責(zé)過重冯丙,在一定程度上違背了“單一職責(zé)原則”。

1.4 單例的生命周期#

下面的表格展示了程序中中不同的變量在手機(jī)存儲(chǔ)器中的存儲(chǔ)位置遭京;

位置 存放的變量
臨時(shí)變量(由編譯器管理自動(dòng)創(chuàng)建/分配/釋放的胃惜,棧中的內(nèi)存被調(diào)用時(shí)處于存儲(chǔ)空間中,調(diào)用完畢后由系統(tǒng)系統(tǒng)自動(dòng)釋放內(nèi)存)
通過 alloc哪雕、calloc船殉、malloc 或 new 申請(qǐng)內(nèi)存,由開發(fā)者手動(dòng)在調(diào)用之后通過 free 或 delete 釋放內(nèi)存斯嚎。動(dòng)態(tài)內(nèi)存的生存期可以由我們決定利虫,如果我們不釋放內(nèi)存挨厚,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存,在ARC模式下糠惫,由系統(tǒng)自動(dòng)管理疫剃。
全局區(qū)域 靜態(tài)變量(編譯時(shí)分配,APP 結(jié)束時(shí)由系統(tǒng)釋放)
常量 常量(編譯時(shí)分配硼讽,APP結(jié)束時(shí)由系統(tǒng)釋放)
代碼區(qū) 存放代碼

在程序中巢价,一個(gè)單例類在程序中只能初始化一次,為了保證在使用中始終都是存在的固阁,所以單例是在存儲(chǔ)器的全局區(qū)域壤躲,在編譯時(shí)分配內(nèi)存,只要程序還在運(yùn)行就會(huì)一直占用內(nèi)存备燃,在 APP 結(jié)束后由系統(tǒng)釋放這部分內(nèi)存內(nèi)存柒爵。

單例的靜態(tài)變量被置為 nil,是否內(nèi)存會(huì)得到釋放赚爵?

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`static Singletion * singleton;

  • (void)dealloc
    {
    NSLog(@"%s", func);
    }

Singleton * s = [Singleton sharedSingleton];
s = nil;
singleton = nil;` </pre>

將單例類實(shí)例對(duì)象賦值 nil 后,會(huì)觸發(fā)單例的 dealloc 方法法瑟。

靜態(tài)變量修飾的指針保存在了全局區(qū)域冀膝,不會(huì)被釋放。但是指針保存的首地址關(guān)聯(lián)的對(duì)象是保存在堆區(qū)的霎挟,是會(huì)被釋放的窝剖。

二、單例的實(shí)現(xiàn)

單例的實(shí)現(xiàn)重點(diǎn)就是防止在外部調(diào)用的時(shí)候出現(xiàn)多個(gè)不同的實(shí)例酥夭,也就是說要從創(chuàng)建的方式入手禁止出現(xiàn)多個(gè)不同的實(shí)例赐纱。

主要是做到以下幾點(diǎn):

  1. 防止調(diào)用 [[A alloc] init] 引起的錯(cuò)誤

  2. 防止調(diào)用 new 引起的錯(cuò)誤

  3. 防止調(diào)用 copy 引起的錯(cuò)誤

  4. 防止調(diào)用 mutableCopy 引起的錯(cuò)誤

2.1 實(shí)現(xiàn)方式一#

把所有可能出現(xiàn)的初始化方法做了相應(yīng)的處理來其保證安全性

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`+ (instancetype)sharedSingleton
{
static Singleton *_sharedSingleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 不能再使用 alloc 方法
// 因?yàn)橐呀?jīng)重寫了 allocWithZone 方法,所以這里要調(diào)用父類的分配空間的方法
_sharedSingleton = [[super allocWithZone:NULL] init];
});
return _sharedSingleton;
}

// ②熬北、防止 [[A alloc] init] 和 new 引起的錯(cuò)誤疙描。因?yàn)?[[A alloc] init] 和 new 實(shí)際是一樣的工作原理,都是執(zhí)行了下面方法

  • (instancetype)allocWithZone:(struct _NSZone *)zone
    {
    return [Singleton sharedSingleton];
    }

// ③讶隐、NSCopying 防止 copy 引起的錯(cuò)誤起胰。當(dāng)你的單例類不遵循 NSCopying 協(xié)議,外部調(diào)用本身就會(huì)出錯(cuò).

  • (id)copyWithZone:(nullable NSZone *)zone
    {
    return [Singleton sharedSingleton];
    }

// ④巫延、防止 mutableCopy 引起的錯(cuò)誤效五,當(dāng)你的單例類不遵循 NSMutableCopying 協(xié)議,外部調(diào)用本身就會(huì)出錯(cuò).

  • (id)mutableCopyWithZone:(nullable NSZone *)zone
    {
    return [Singleton sharedSingleton];
    }` </pre>

dispatch_once 主要是根據(jù) onceToken 的值來決定怎么去執(zhí)行代碼炉峰。

  1. 當(dāng) onceToken = 0 時(shí)畏妖,線程執(zhí)行 dispatch_once 的 block 中代碼;
  2. 當(dāng) onceToken = -1 時(shí)疼阔,線程跳過 dispatch_once 的 block 中代碼不執(zhí)行戒劫;
  3. 當(dāng) onceToken 為其他值時(shí)半夷,線程被阻塞,等待 onceToken 值改變谱仪。

當(dāng)線程調(diào)用 shareInstance玻熙,此時(shí) onceToken = 0,調(diào)用 block 中的代碼疯攒,此時(shí) onceToken 的值變?yōu)?140734537148864嗦随。當(dāng)其他線程再調(diào)用 shareInstance 方法時(shí),onceToken 的值已經(jīng)是 140734537148864 了敬尺,線程阻塞枚尼。當(dāng) block 線程執(zhí)行完 block 之后,onceToken 變?yōu)?-1砂吞,其他線程不再阻塞署恍,跳過 block。下次再調(diào)用 shareInstance 時(shí)蜻直,block 已經(jīng)為 -1盯质,直接跳過 block。

2.2 實(shí)現(xiàn)方式二#

不做處理的情況下禁止外部調(diào)用

一些成熟的第三方代碼的單例中也有使用該方法的概而。

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`.h 文件

  • (instancetype)init NS_UNAVAILABLE;
  • (instancetype)new NS_UNAVAILABLE;
  • (id)copy NS_UNAVAILABLE;
  • (id)mutableCopy NS_UNAVAILABLE;

.m 文件

  • (instancetype)sharedSingleton
    {
    static Singleton *_sharedSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _sharedSingleton = [[self alloc] init]; // 要使用 self 來調(diào)用
    });
    return _sharedSingleton;
    }` </pre>

當(dāng)運(yùn)行 [[A alloc] init] 或 [A new] 時(shí)呼巷,會(huì)直接報(bào)錯(cuò) 'init' is unavailable 或 'new' is unavailable。

三赎瑰、單例的濫用

3.1 全局狀態(tài)#

大多數(shù)的開發(fā)者都認(rèn)同使用全局可變的狀態(tài)是不好的行為王悍。有狀態(tài)使得程序難以理解和難以調(diào)試庇忌。面向?qū)ο蟮某绦騿T在最小化代碼的有狀態(tài)性方面纲熏,有很多還需要向函數(shù)式編程學(xué)習(xí)的地方。

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@implementation SPMath
{
NSInteger _a;
NSInteger _b;
}

  • (NSInteger)add
    {
    return _a + _b;
    }` </pre>

在上面這個(gè)簡單的數(shù)學(xué)庫的實(shí)現(xiàn)中苍息,程序員需要在調(diào)用 add 前正確的設(shè)置實(shí)例變量 _a 和 _b源譬。這樣有以下問題:

  1. add 沒有顯式的通過使用參數(shù)的形式聲明它依賴于 _a 和 _b 的狀態(tài)集惋。與僅僅通過查看函數(shù)聲明就可以知道這個(gè)函數(shù)的輸出依賴于哪些變量不同的是,另一個(gè)開發(fā)者必須查看這個(gè)函數(shù)的具體實(shí)現(xiàn)才能明白這個(gè)函數(shù)依賴那些變量踩娘。隱藏依賴是不好的芋膘。

  2. 當(dāng)修改 _a 和 _b 的數(shù)值為調(diào)用 add 做準(zhǔn)備時(shí),程序員需要保證修改不會(huì)影響任何其他依賴于這兩個(gè)變量的代碼的正確性霸饲。而這在多線程的環(huán)境中是尤其困難的为朋。

把下面的代碼和上面的例子做對(duì)比:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

+ (NSUInteger)addOf:(NSUInteger)a plus:(NSUInteger)b { return a + b; } </pre>

這里,對(duì)變量 a 和 b 的依賴被顯式的聲明了厚脉,并且不需要為了調(diào)用這個(gè)方法而去改變實(shí)例變量的狀態(tài)习寸,也不需要擔(dān)心調(diào)用這個(gè)函數(shù)會(huì)留下持久的副作用。甚至可以聲明為類方法傻工,這樣就顯式的告訴了代碼的閱讀者:這個(gè)方法不會(huì)修改任何實(shí)例的狀態(tài)霞溪。

那么孵滞,這個(gè)例子和單例相比又有什么關(guān)系呢?用 Mi?ko Hevery 的話來說鸯匹,“單例就是披著羊皮的全局狀態(tài)” 坊饶。

一個(gè)單例可以在不需要顯式聲明對(duì)其依賴的情況下,被使用在任何地方殴蓬。就像變量 _a 和 _b 在 add 內(nèi)部被使用了匿级,卻沒有被顯式聲明一樣,程序的任意模塊都可以調(diào)用 [A sharedInstance] 并且訪問這個(gè)單例染厅。這意味著任何和這個(gè)單例交互產(chǎn)生的副作用都會(huì)影響程序其他地方的任意代碼痘绎。

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@interface Singleton : NSObject

  • (instancetype)sharedInstance;
  • (NSString *)name;
  • (void)setName:(NSString *)name;

@end

@implementation A

  • (void)a
    {
    if ([[Singleton sharedInstance] name]) {
    // ...
    }
    }

@end

@implementation B

  • (void)b
    {
    [[Singleton sharedInstance] setName:""];
    }

@end` </pre>

在上面的代碼中,A 和 B 是兩個(gè)完全獨(dú)立的模塊肖粮。但是 B 可以通過使用單例提供的共享狀態(tài)來影響 A 的行為孤页。這種情況應(yīng)該只能發(fā)生在 B 顯式引用了 A,顯式建立了它們兩者之間的關(guān)系時(shí)涩馆。由于這里使用了單例行施,單例的全局性和有狀態(tài)性,導(dǎo)致隱式的在兩個(gè)看起來完全不相關(guān)的模塊之間建立了耦合魂那。

來看一個(gè)更具體的例子悲龟,并且暴露一個(gè)使用全局可變狀態(tài)的額外問題。

想要在我們的應(yīng)用中構(gòu)建一個(gè)網(wǎng)頁查看器(web viewer)冰寻。我們構(gòu)建了一個(gè)簡單的 URL cache 來支持這個(gè)網(wǎng)頁查看器:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@interface URLCache

  • (NSCache *)sharedURLCache;
  • (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;

@end` </pre>

這個(gè)開發(fā)者開始寫了一些單元測(cè)試來保證代碼在不同的情況下都能達(dá)到預(yù)期。首先皿渗,他寫了一個(gè)測(cè)試用例來保證網(wǎng)頁查看器在沒有設(shè)備鏈接時(shí)能夠展示出錯(cuò)誤信息斩芭。然后他寫了一個(gè)測(cè)試用例來保證網(wǎng)頁查看器能夠正確的處理服務(wù)器錯(cuò)誤。最后乐疆,他為成功情況時(shí)寫了一個(gè)測(cè)試用例划乖,來保證返回的網(wǎng)絡(luò)內(nèi)容能夠被正確的顯示出來。這個(gè)開發(fā)者運(yùn)行了所有的測(cè)試用例挤土,并且它們都如預(yù)期一樣正確琴庵。

幾個(gè)月以后,這些測(cè)試用例開始出現(xiàn)失敗仰美,盡管網(wǎng)頁查看器的代碼從它寫完后就從來沒有再改動(dòng)過迷殿!到底發(fā)生了什么?

原來咖杂,有人改變了測(cè)試的順序庆寺。處理成功的那個(gè)測(cè)試用例首先被運(yùn)行,然后再運(yùn)行其他兩個(gè)诉字。處理錯(cuò)誤的那兩個(gè)測(cè)試用例現(xiàn)在竟然成功了懦尝,和預(yù)期不一樣知纷,因?yàn)?URL cache 這個(gè)單例把不同測(cè)試用例之間的 response 緩存起來了。

持久化狀態(tài)是單元測(cè)試的敵人陵霉,因?yàn)閱卧獪y(cè)試在各個(gè)測(cè)試用例相互獨(dú)立的情況下才有效琅轧。如果狀態(tài)從一個(gè)測(cè)試用例傳遞到了另外一個(gè),這樣就和測(cè)試用例的執(zhí)行順序就有關(guān)系了踊挠。有 bug 的測(cè)試用例是非常糟糕的事情乍桂,特別是那些有時(shí)候能通過測(cè)試,有時(shí)候又不能通過測(cè)試的止毕。

3.2 對(duì)象的生命周期#

另外一個(gè)關(guān)鍵問題就是單例的生命周期模蜡。當(dāng)你在程序中添加一個(gè)單例時(shí),很容易會(huì)認(rèn)為 “它們永遠(yuǎn)只能有一個(gè)實(shí)例”扁凛。但是在很多我看到過的 iOS 代碼中忍疾,這種假定都可能被打破。

假設(shè)我們正在構(gòu)建一個(gè)應(yīng)用谨朝,在這個(gè)應(yīng)用里用戶可以看到他們的好友列表卤妒。他們的每個(gè)朋友都有一張個(gè)人信息的圖片,并且我們想使我們的應(yīng)用能夠下載并且在設(shè)備上緩存這些圖片字币。 使用 dispatch_once 代碼片段则披,寫一個(gè) ThumbnailCache 單例:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@interface ThumbnailCache : NSObject

  • (instancetype)sharedThumbnailCache;
  • (void)cacheProfileImage:(NSData *)imageData forUserId:(NSString *)userId;
  • (NSData *)cachedProfileImageForUserId:(NSString *)userId;

@end` </pre>

繼續(xù)構(gòu)建我們的應(yīng)用,一切看起來都很正常洗出,直到有一天士复,決定實(shí)現(xiàn)“注銷”功能時(shí),這樣用戶可以在應(yīng)用中進(jìn)行賬號(hào)切換翩活。突然發(fā)現(xiàn)我們將要面臨一個(gè)討厭的問題:用戶相關(guān)的狀態(tài)存儲(chǔ)在全局單例中阱洪。

當(dāng)用戶注銷后,我們希望能夠清理掉所有的硬盤上的持久化狀態(tài)菠镇。否則冗荸,我們將會(huì)把這些被遺棄的數(shù)據(jù)殘留在用戶的設(shè)備上,浪費(fèi)寶貴的硬盤空間利耍。對(duì)于用戶登出又登錄了一個(gè)新的賬號(hào)這種情況蚌本,我們也想能夠?qū)@個(gè)新用戶使用一個(gè)全新的 ThumbnailCache 實(shí)例。

問題在于按照定義單例被認(rèn)為是“創(chuàng)建一次隘梨,永久有效”的實(shí)例程癌。你可以想到一些對(duì)于上述問題的解決方案≈崃裕或許我們可以在用戶登出時(shí)移除這個(gè)單例:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`static ThumbnailCache * sharedThumbnailCache;

  • (instancetype)sharedThumbnailCache
    {
    if (!sharedThumbnailCache) {
    sharedThumbnailCache = [[self alloc] init];
    }
    return sharedThumbnailCache;
    }

  • (void)cleanUp
    {
    // The SPThumbnailCache will clean up persistent states when deallocated
    sharedThumbnailCache = nil;
    }` </pre>

這是一個(gè)明顯的對(duì)單例模式的濫用席楚,但是它可以工作,對(duì)吧税稼。

當(dāng)然可以使用這種方式去解決烦秩,但代價(jià)實(shí)在是太大了垮斯。我們不能使用簡單的、能夠保證線程安全和所有的調(diào)用 [ThumbnailCache sharedThumbnailCache] 的地方都會(huì)訪問同一個(gè)實(shí)例的 dispatch_once 解決方案了≈混簦現(xiàn)在我們需要對(duì)使用 thumbnail cache 時(shí)的代碼的執(zhí)行順序非常小心兜蠕。假設(shè)當(dāng)用戶正在執(zhí)行登出操作時(shí),有一些后臺(tái)任務(wù)正在執(zhí)行把圖片保存到緩存中的操作:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[ThumbnailCache sharedThumbnailCache] cacheProfileImage:newImage forUserId:userId]; }); </pre>

需要保證在所有的后臺(tái)任務(wù)完成前抛寝, cleanUp 一定不能被執(zhí)行熊杨。這保證了 newImage 可以被正確的清理掉〉两ⅲ或者晶府,我們需要保證在 thumbnail cache 被移除時(shí),后臺(tái)緩存任務(wù)一定要被取消掉钻趋。否則川陆,一個(gè)新的 thumbnail cache 的實(shí)例將會(huì)被延遲創(chuàng)建,并且之前用戶的數(shù)據(jù)(newImage 對(duì)象)會(huì)被存儲(chǔ)在它里面蛮位。

由于對(duì)于單例實(shí)例來說它沒有明確的所有者较沪,(比如,單例自己管理自己的生命周期)失仁,永遠(yuǎn)“關(guān)閉”一個(gè)單例變得非常的困難尸曼。

分析到這里,希望能夠意識(shí)到萄焦,這個(gè) thumbnail cache 從來就不應(yīng)該作為一個(gè)單例控轿。問題在于一個(gè)對(duì)象的生命周期可能在項(xiàng)目的最初階段沒有被很好得考慮清楚。

舉一個(gè)具體的例子拂封,Dropbox 的 iOS 客戶端曾經(jīng)只支持一個(gè)賬號(hào)登錄茬射。它以這樣的狀態(tài)存在了數(shù)年,直到有一天我們希望能夠同時(shí)支持多個(gè)用戶賬號(hào)登錄(既包括個(gè)人賬號(hào)也包括企業(yè)賬號(hào))烘苹。突然之間,我們以前的的假設(shè)“只能夠同時(shí)有一個(gè)用戶處于登錄狀態(tài)”就不成立了片部。 假定一個(gè)對(duì)象的生命周期和應(yīng)用的生命周期一致镣衡,會(huì)限制你的代碼的靈活擴(kuò)展,早晚有一天當(dāng)產(chǎn)品的需求產(chǎn)生變化時(shí)档悠,你會(huì)為當(dāng)初的這個(gè)假定付出代價(jià)的廊鸥。

這里我們得到的教訓(xùn)是:單例應(yīng)該只用來保存全局的狀態(tài),并且不能和任何作用域綁定辖所。如果這些狀態(tài)的作用域比一個(gè)完整的應(yīng)用程序的生命周期要短惰说,那么這個(gè)狀態(tài)就不應(yīng)該使用單例來管理。用一個(gè)單例來管理用戶綁定的狀態(tài)缘回,是代碼的壞味道吆视,你應(yīng)該認(rèn)真的重新評(píng)估你的對(duì)象圖的設(shè)計(jì)典挑。

四、避免使用單例

既然單例對(duì)局部作用域的狀態(tài)有這么多的壞處啦吧,那么應(yīng)該怎樣避免使用它們呢您觉?

重溫上面的例子。既然我們的 thumbnail cache 的緩存狀態(tài)是和具體的用戶綁定的授滓,那么定義一個(gè) user 對(duì)象吧琳水。

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@interface User : NSObject
@property (nonatomic, readonly) ThumbnailCache * thumbnailCache;
@end

@implementation User

  • (instancetype)init
    {
    if ((self = [super init])) {
    _thumbnailCache = [[ThumbnailCache alloc] init];
    }
    return self;
    }

@end` </pre>

現(xiàn)在用一個(gè)對(duì)象來作為一個(gè)經(jīng)過認(rèn)證的用戶會(huì)話的模型類,并且可以把所有和用戶相關(guān)的狀態(tài)存儲(chǔ)在這個(gè)對(duì)象中般堆。

現(xiàn)在假設(shè)我們有一個(gè) VC 來展現(xiàn)好友列表:

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

`@interface FriendListVC : UIViewController

  • (instancetype)initWithUser:(User *)user;

@end` </pre>

我們可以顯式的把經(jīng)過認(rèn)證的 user 對(duì)象作為參數(shù)傳遞給這個(gè) vc在孝。這種把依賴性傳遞給依賴對(duì)象的技術(shù)正式的叫法是依賴注入,并且它有很多優(yōu)點(diǎn):

①淮摔、對(duì)于閱讀這個(gè) FriendListVC 頭文件的人來說私沮,可以很清楚的知道它只有在有登錄用戶的情況下才會(huì)被展示。

②噩咪、這個(gè) FriendListVC 只要還在使用中顾彰,就可以強(qiáng)引用 user 對(duì)象。

<pre class="line-numbers" style="margin: 0px; padding: 0px; overflow: auto; white-space: pre !important; position: relative !important;">

Copy

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_user.thumbnailCache cacheProfileImage:newImage forUserId:userId]; }); </pre>

這種后臺(tái)任務(wù)仍然意義重大胃碾,當(dāng)?shù)谝粋€(gè)實(shí)例失效時(shí)涨享,應(yīng)用其他地方的代碼可以創(chuàng)建和使用一個(gè)全新的 User 對(duì)象,而不會(huì)阻塞用戶交互仆百。

為了更詳細(xì)的說明一下第二點(diǎn)厕隧,讓我們畫一下在使用依賴注入之前和之后的對(duì)象圖。

  1. 假設(shè) FriendListVC 是當(dāng)前 window 的 root view controller俄周。使用單例時(shí)吁讨,對(duì)象圖看起來如下所示:

?

vc 以及自定義的 imageView,都會(huì)和 sharedThumbnailCache 產(chǎn)生交互峦朗。

當(dāng)用戶登出后建丧,清理 rootViewController 并且退出到登錄頁面:

這里的問題在于這個(gè) FriendListVC 可能仍然在執(zhí)行代碼(由于后臺(tái)操作的原因),并且可能因此仍然有一些調(diào)用被掛起到 sharedThumbnailCache 上波势。

  1. 使用依賴注入的對(duì)象圖:

簡單起見翎朱,假設(shè) UIApplicationDelegate 管理 User 的實(shí)例(在實(shí)際中,為了簡化 applicationDelegate 可能會(huì)把這些用戶狀態(tài)的管理工作交給另外一個(gè)對(duì)象來做)尺铣。當(dāng)展現(xiàn) FriendListVC 時(shí)拴曲,會(huì)傳遞進(jìn)去一個(gè) user 的引用。這個(gè)引用也會(huì)向下傳遞給 profileImageView×莘蓿現(xiàn)在澈灼,當(dāng)用戶登出時(shí),我們的對(duì)象圖如下所示:

這個(gè)對(duì)象圖看起來和使用單例時(shí)很像。這有什么區(qū)別叁熔?

關(guān)鍵問題是作用域委乌。在單例情況下,sharedThumbnailCache 仍然可以被程序的任意模塊訪問者疤。假如用戶快速的登錄了一個(gè)新的賬號(hào)福澡。該用戶也想看看他的好友列表,這也就意味著需要再一次的和 thumbnailCache 產(chǎn)生交互:

當(dāng)用戶登錄一個(gè)新賬號(hào)驹马,我們應(yīng)該能夠構(gòu)建并且與全新的 ThumbnailCache 交互革砸,而不需要再在銷毀老的 thumbnailCache 上花費(fèi)精力∨蠢郏基于對(duì)象管理的典型規(guī)則算利,舊的 vc 和老的 thumbnailCache 應(yīng)該能夠自己在后臺(tái)延遲被清理掉。簡而言之泳姐,我們應(yīng)該隔離用戶 A 相關(guān)聯(lián)的狀態(tài)和用戶 B 相關(guān)聯(lián)的狀態(tài):

五效拭、結(jié)論

在 iOS 開發(fā)的世界中,單例的使用是如此的普遍以至于我們有時(shí)候忘記了多年來在其他面向?qū)ο缶幊讨袑W(xué)到的教訓(xùn)胖秒。

這一切的關(guān)鍵點(diǎn)在于缎患,在面向?qū)ο缶幊讨形覀兿胍钚』勺儬顟B(tài)的作用域。但是單例卻站在了對(duì)立面阎肝,因?yàn)樗鼈兪箍勺兊臓顟B(tài)可以被程序中的任何地方訪問挤渔。下一次使用單例時(shí),希望能夠好好考慮一下使用依賴注入作為替代方案风题。

轉(zhuǎn)載:https://www.cnblogs.com/dins/p/ios-singleton.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末判导,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沛硅,更是在濱河造成了極大的恐慌眼刃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇肌,死亡現(xiàn)場離奇詭異擂红,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)围小,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門昵骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吩抓,你說我怎么就攤上這事涉茧「昂蓿” “怎么了疹娶?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伦连。 經(jīng)常有香客問我雨饺,道長钳垮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任额港,我火速辦了婚禮饺窿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘移斩。我一直安慰自己肚医,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布向瓷。 她就那樣靜靜地躺著肠套,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猖任。 梳的紋絲不亂的頭發(fā)上你稚,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音朱躺,去河邊找鬼刁赖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛长搀,可吹牛的內(nèi)容都是我干的宇弛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盈滴,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼涯肩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巢钓,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤病苗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后症汹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硫朦,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年背镇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咬展。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞒斩,死狀恐怖破婆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胸囱,我是刑警寧澤祷舀,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響裳扯,放射性物質(zhì)發(fā)生泄漏抛丽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一饰豺、第九天 我趴在偏房一處隱蔽的房頂上張望亿鲜。 院中可真熱鬧,春花似錦冤吨、人聲如沸蒿柳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽其馏。三九已至,卻和暖如春爆安,著一層夾襖步出監(jiān)牢的瞬間叛复,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工扔仓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褐奥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓翘簇,卻偏偏與公主長得像撬码,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子版保,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355