物件記憶體的回收
開發中,物件管理的基本原則——誰建立誰釋放。但是,非ARC工程中,我們會用autorelease來標記一個物件,告訴編輯器,這個物件我不負責釋放,此時,這個物件就變成了“自釋放”物件,當其不再需要時,系統就會自動回收其記憶體。而ARC工程中,所有物件對於我們來說都是自釋放物件,很高興,我們不再需要處處留意記憶體洩露的問題,可以把更多的精力放在業務邏輯上,但是這並不意味著真的沒有記憶體洩露,試試這個工具HJNSObjectRelease,也許你會有意想不到的收穫。
定時器的自釋放
定時器與一般物件不同,當建立完定時器後,其並不會自我釋放,需要在適當時刻invalidate。在實際開發中,也許你經常會這樣建立定時器
1 |
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,也許你會這樣寫程式碼
1 2 3 4 5 6 7 8 9 10 |
- (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可以這樣寫
1 2 3 |
[self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { clockView.date = change[NSKeyValueChangeNewKey]; }]; |
FBKVOController的實現原理可以檢視這篇文章,通過自釋放的實現,程式猿不再關心remove監聽。但是其還是有一定的侷限性——物件無法監聽自己的屬性,如果你的程式碼是這樣的
1 2 3 |
[self.KVOController observe:self keyPath:@"date" options:NSKeyValueObservingOptionNew block:^(NSDictionary *change) { // to do }]; |
很遺憾,迴圈引用的問題又出現,因為FBKVOController中的NSMapTable物件會retain key物件,具體程式碼如下
1 |
[_objectInfosMap setObject:infos forKey:object]; |
那麼,FBKVOController是如何做到自釋放的?可以歸納為四個字——動態屬性。其為觀察者繫結動態屬性self.KVOController,動態繫結的KVOController會隨著觀察者的釋放而釋放,KVOController在自己的dealloc函式中移除KVO監聽,巧妙的將觀察者的remove轉移到其動態屬性的dealloc函式中。
可是,這又有什麼用?物件仍然無法監聽自己的屬性,還是要重寫set函式。HTBKVObservation也許會改變你的想法,其和FBKVOController來自同一人,程式碼可以這樣寫
1 2 3 |
self.anObservation = [HTBKVObservation observe:anObjectToObserve keyPath:@"observeMe" options:0 callback:^(HTBKVObservation *observation, NSDictionary *changeDictionary) { // to do }]; |
HTBKVObservation並沒用採用動態屬性,而是採用屬性的方式實現自釋放。可以監控物件自己的屬性,但是需要建立屬性HTBKVObservation。 這裡我對其做了一點擴充套件,方便使用,程式碼可以這樣寫
1 2 3 |
[self observe:self keyPath:@"KVOPath" options:NSKeyValueObservingOptionNew callback:^(HTBKVObservation *observation, NSDictionary *changeDictionary) { // to do }]; |
FBKVOController 和HTBKVObservation通過屬性或動態屬性巧妙的將KVO的remove轉移給第三者,實現了KVO事件的解耦,為自釋放的實現提供了一種借鑑思路
NSNotification的自釋放
談完 KVO,再來談談NSNotification。針對Notification,ReactiveCocoa做了很好的封裝,網上有很多介紹其如何使用的文章,在此不再累述。直接看程式碼
1 2 3 4 |
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidChangeFrameNotification object:nil] subscribeNext:^(id x) { // to do } ]; |
簡單明瞭,當觀察者dealloc,很遺憾,NSNotification並沒用移除,因為物件並沒用自釋放,正確程式碼應該是這樣
1 2 3 4 |
[[[[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的自釋放
可以根據具體業務或者設計思想選擇對應的實現方式,這是一種思想,更是一個習慣。相信你會愛上它,畢竟誰不喜歡簡潔的實現方式了!