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