weak 關(guān)鍵字的運用在 iOS 當(dāng)中屬于基礎(chǔ)知識,在面試的時候問 weak 的用處,就像兩個 iOS 程序員見面寒暄問候一樣普通了。
weak 的常見場景是在 delegate,block,NSTimer 中使用,以避免循環(huán)引用所帶來的內(nèi)存泄漏,這是教科書式的用法。
編程語言是工具,語言特性只是工具的特性,工具怎么用在于使用者。weak 關(guān)鍵字的方便之處絕不局限于避免循環(huán)引用,適當(dāng)腦洞,可以在其他場景下帶來一些有趣的應(yīng)用。
weak 的用處用一句話可歸納為:弱引用,在對象釋放后置為 nil,避免錯誤的內(nèi)存訪問。用更通俗的話來表述是:weak 可以在不增加對象的引用計數(shù)的同時,又使得指針的訪問是安全的。
weak singleton
之前見過一篇文章介紹了一個新 pattern 叫 「weak singleton」,原文出處點我。這種特殊的單例有一個有意思的特性:在所有使用該單例的對象都釋放后,單例對象本身也會自己釋放。我所見過的大部分單例使用場景,被創(chuàng)建都單例最后都會一直存活著,比如注冊登錄模塊所需要共享狀態(tài)所創(chuàng)建的 XXLoginManager,即使在用戶注冊成功進入主界面之后也不會被顯式的釋放,這在一定程度上會帶來內(nèi)存使用的浪費。所謂的「weak singleton」代碼很簡單:
+ (id)sharedInstance
{
static __weak ASingletonClass *instance;
ASingletonClass *strongInstance = instance;
@synchronized(self) {
if (strongInstance == nil) {
strongInstance = [[[self class] alloc] init];
instance = strongInstance;
}
}
return strongInstance;
}
Controller A, B, C 都可以持有 ASingletonClass 的強引用,一旦 A,B,C 都銷毀后,ASingletonClass 的單例對象也會隨之銷毀,略巧妙不是嗎?
「weak singleton」這個漂亮名字背后其實只是簡單而巧妙的利用了 weak 特性,sharedInstance 中的 weak 就像是一個智能管家,在無人使用 instance 之后就置為 nil 銷毀,當(dāng) sharedInstance 再次被調(diào)用時,instance 又會重新被創(chuàng)建。
weak associated object
當(dāng)我們需要給已有的功能模塊添加新功能特性的時候,比如給所有的 UIViewController 添加一個 dumpViewHierarchy 方法,可以把當(dāng)前 Controller 的 view 結(jié)構(gòu)完整保存來下并上報服務(wù)器,我們有幾種思路可供選擇:
方案一:定義一個新的父類 DumpViewController,繼承該父類的子類可以獲得 dumpViewHierarchy 方法。
方案二:定義一個新的 DumpViewObject 類,已有的 Controller 只需要創(chuàng)建一個 DumpViewObject 對象,并調(diào)用 dumpViewHierarchy 方法,傳入 self 即可。
方案三:給已有的 Controller 類添加一個 Category,XXController + DumpView,并在 Category 中實現(xiàn) dumpViewController 方法,有時候我們還需要做一些狀態(tài)保存,所以擴展性更好的辦法是使用 associated object 給 Category 添加一個 DumpViewObject property,將 dumpView 相關(guān)的邏輯都寫入 DumpViewObject 類中。
方案四:使用 AOP 的方式,利用 Objective C 的 rumtime 特性 hook 每個 Controller 的 dumpViewHierarchy 方法,并在當(dāng)中實現(xiàn)相應(yīng)邏輯。
方案一,二都對已有代碼改動較大,方案四改動最小,神不知鬼不覺,dumpViewHierarchy 方法甚至可以不出現(xiàn)在 Controller 里面,但這也導(dǎo)致代碼管理上比較松散。方案三是我個人比較推崇的方式,代碼侵入少,同時方法調(diào)用邏輯也會出現(xiàn)在合適的地方,不少知名的第三方庫都使用過這種方式來添加功能,比如 facebook 開源的 FBKVOController,就通過 associated object 的方式給每個 NSObject 對象添加了一個功能屬性。
使用 associated object 的時候,有一些細(xì)節(jié)需要額外考慮。比如 property 是強引用還是弱引用,這個選擇題取決于代碼結(jié)構(gòu)的設(shè)計。如果是強引用,則對象的生命周期跟隨所依附的對象,XXController dealloc 的時候,DumpViewObject 也隨之 dealloc。如果是弱引用,則說明 DumpViewObject 對象的創(chuàng)建會銷毀由其他對象負(fù)責(zé),一般是為了避免存在循環(huán)引用,或者由于 DumpViewObject 的職責(zé)多于所依附對象的需要,DumpViewObject 有更多的狀態(tài)需要維護處理。
associated object 本身并不支持添加具備 weak 特性的 property,但我們可以通過一個小技巧來完成:
- (void)setContext:(CDDContext*)object {
id __weak weakObject = object;
id (^block)() = ^{ return weakObject; };
objc_setAssociatedObject(self, @selector(context), block, OBJC_ASSOCIATION_COPY);
}
- (CDDContext*)context {
id (^block)() = objc_getAssociatedObject(self, @selector(context));
id curContext = (block ? block() : nil);
return curContext;
}
添加了一個中間角色 block,再輔以 weak 關(guān)鍵字就實現(xiàn)了具備 weak 屬性的 associated object。這種做法也印證了軟件工程里一句名言「We can solve any problem by introducing an extra level of indirection」。
類似的用法還有不少,比如 NSArray,NSDictionary 中的元素引用都是強引用,但我們可以通過添加一個中間對象 WeakContainer,WeakContainer 中再通過 weak property 指向目標(biāo)元素,這樣就能簡單的實現(xiàn)一個元素弱引用的集合類。
編程語言一直處于進化當(dāng)中,語言的設(shè)計者會站在宏觀的角度,結(jié)合行業(yè)的需要,添加更多的方便特性,如果只是記住官方文檔里的幾個應(yīng)用場景,而不去思考背后的設(shè)計思路,則很難寫出有想象力的代碼。