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