http://www.olinone.com/
今天,跟大家聊聊“自釋放”思想在iOS開發中的應用,何為“自釋放”?可以簡單的理解為對象在生命周期結束後自動清理回收所有與其相關的資源或鏈接,這個清理不僅僅包括對象內存的回收,還包括對象解耦以及附屬事件的清理等,比如定時器的自我停止、KVO對象的監聽移除等
對象內存的回收
開發中,對象管理的基本原則——誰創建誰釋放。但是,非ARC工程中,我們會用autorelease來標記一個對象,告訴編輯器,這個對象我不負責釋放,此時,這個對象就變成了“自釋放”對象,當其不再需要時,系統就會自動回收其內存。而ARC工程中,所有對象對於我們來說都是自釋放對象,很高興,我們不再需要處處留意內存洩露的問題,可以把更多的精力放在業務邏輯上,但是這並不意味著真的沒有內存洩露,試試這個工具HJNSObjectRelease,也許你會有意想不到的收獲。
定時器的自釋放
定時器與一般對象不同,當創建完定時器後,其並不會自我釋放,需要在適當時刻invalidate。在實際開發中,也許你經常會這樣創建定時器
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(onTimerCount) userInfo:nil repeats:YES];
然後在dealloc函數中將定時器invalidate。很遺憾,你會發現程序永遠也不會執行到dealloc函數,因為NSTimer強引用target對象,循環引用的出現必然導致內存洩露。此時,你肯定非常想要一個weak target的定時器,很高興, MSWeakTimer 很好的滿足了你的需求。但是,Timer仍然沒有自我釋放,你仍然需要在dealloc中將其invalidate。那麼,如何才能不寫invalidate?定時器能否自釋放?我們先把這個問題放在一邊,接著往下看
KVO的自釋放
iOS開發中,經常會用到消息通知及KVO,也許你會這樣寫代碼
- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNotice) name:@"NoticeIdentifier" object:nil]; [self addObserver:target forKeyPath:@"keyPath" options:NSKeyValueObservingOptionNew context:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"NoticeIdentifier" object:nil]; [self removeObserver:target forKeyPath:@"keyPath"]; }
隨著時間的積累,你會非常習慣這種寫法,並且蘋果也是這樣推薦的。但是慢慢你會發現所有對象的Dealloc函數都只做了這一件事,能不能不做這件事? FBKVOController 也許會是一個不錯的選擇,Demo可以這樣寫
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { clockView.date = change[NSKeyValueChangeNewKey]; }];
FBKVOController 的實現原理可以查看這篇 文章 ,通過自釋放的實現,程序猿不再關心remove監聽。但是其還是有一定的局限性——對象無法監聽自己的屬性,如果你的代碼是這樣的
[self.KVOController observe:self keyPath:@"date" options:NSKeyValueObservingOptionNew block:^(NSDictionary *change) { // to do }];
很遺憾,循環引用的問題又出現,因為 FBKVOController 中的NSMapTable對象會retain key對象,具體代碼如下
[_objectInfosMap setObject:infos forKey:object];
那麼, FBKVOController 是如何做到自釋放的?可以歸納為四個字——動態屬性。其為觀察者綁定動態屬性self.KVOController,動態綁定的KVOController會隨著觀察者的釋放而釋放,KVOController在自己的dealloc函數中移除KVO監聽,巧妙的將觀察者的remove轉移到其動態屬性的dealloc函數中。
可是,這又有什麼用?對象仍然無法監聽自己的屬性,還是要重寫set函數。 HTBKVObservation 也許會改變你的想法,其和 FBKVOController 來自同一人,代碼可以這樣寫
self.anObservation = [HTBKVObservation observe:anObjectToObserve keyPath:@"observeMe" options:0 callback:^(HTBKVObservation *observation, NSDictionary *changeDictionary) { // to do }];
HTBKVObservation 並沒用采用動態屬性,而是采用屬性的方式實現自釋放。可以監控對象自己的屬性,但是需要創建屬性HTBKVObservation。 這裡 我對其做了一點擴展,方便使用,代碼可以這樣寫
[self observe:self keyPath:@"KVOPath" options:NSKeyValueObservingOptionNew callback:^(HTBKVObservation *observation, NSDictionary *changeDictionary) { // to do }];
FBKVOController 和 HTBKVObservation 通過屬性或動態屬性巧妙的將KVO的remove轉移給第三者,實現了KVO事件的解耦,為自釋放的實現提供了一種借鑒思路
NSNotification的自釋放
談完 KVO,再來談談NSNotification。針對Notification, ReactiveCocoa 做了很好的封裝,網上有很多介紹其如何使用的文章,在此不再累述。直接看代碼
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidChangeFrameNotification object:nil] subscribeNext:^(id x) { // to do } ];
簡單明了,當觀察者dealloc,很遺憾,NSNotification並沒用移除,因為對象並沒用自釋放,正確代碼應該是這樣
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidChangeFrameNotification object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) { // to do } ];
ReactiveCocoa 自釋放的原理與 FBKVOController 不同,其並不是通過屬性或者動態屬性的方式實現,而是通過swizzling觀察對象的dealloc函數,在自定義dealloc函數實施清理,但不是默認清理,需要我們告訴它willDeallocSignal的時候完成所有清理工作。
除了定時器、KVO、NSNotification,包括封裝的某個功能對象,比如HttpRequest,或者數據庫ListSql等,合理的利用自釋放可以給使用者帶來更多的便利,同時也會減少 crash 產生的概率。實現自釋放的方法可以總結為以下三種方式
動態屬性的自釋放
@property 的自釋放
swizzling dealloc的自釋放
可以根據具體業務或者設計思想選擇對應的實現方式,這是一種思想,更是一個習慣。相信你會愛上它,畢竟誰不喜歡簡潔的實現方式了!
後記: 好久沒有更新文章,一是最近任務比較重,接手老代碼,幾千行的Controller實在無能為力,只能重寫。如今,面對不到300行的Controller還是挺有成就感。生命不息,折騰不止!二是想為大家分享一點干貨,不敢輕易提筆。依舊,記得 點贊 ,感謝大家來訪!