“自釋放”在iOS開發中的應用

轉載請注明出處:http://www.olinone.com/

今天,跟大家聊聊“自釋放”思想在iOS開發中的應用,何為“自釋放”?可以簡單的理解為對象在生命周期結束后自動清理回收所有與其相關的資源或鏈接,這個清理不僅僅包括對象內存的回收,還包括對象解耦以及附屬事件的清理等,比如定時器的自我停止、KVO對象的監聽移除等

對象內存的回收

開發中,對象管理的基本原則——誰創建誰釋放。但是,非ARC工程中,我們會用autorelease來標記一個對象,告訴編輯器,這個對象我不負責釋放,此時,這個對象就變成了“自釋放”對象,當其不再需要時,系統就會自動回收其內存。而ARC工程中,所有對象對于我們來說都是自釋放對象,很高興,我們不再需要處處留意內存泄露的問題,可以把更多的精力放在業務邏輯上,但是這并不意味著真的沒有內存泄露,試試這個工具HJNSObjectRelease,也許你會有意想不到的收獲。

定時器的自釋放

定時器與一般對象不同,當創建完定時器后,其并不會自我釋放,需要在適當時刻invalidate。在實際開發中,也許你經常會這樣創建定時器

1

self.timer=[NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(onTimerCount)userInfo:nilrepeats:YES];

然后在dealloc函數中將定時器invalidate。很遺憾,你會發現程序永遠也不會執行到dealloc函數,因為NSTimer強引用target對象,循環引用的出現必然導致內存泄露。此時,你肯定非常想要一個weak target的定時器,很高興,MSWeakTimer很好的滿足了你的需求。但是,Timer仍然沒有自我釋放,你仍然需要在dealloc中將其invalidate。那么,如何才能不寫invalidate?定時器能否自釋放?我們先把這個問題放在一邊,接著往下看

KVO的自釋放

iOS開發中,經常會用到消息通知及KVO,也許你會這樣寫代碼

-(void)viewDidLoad{

[superviewDidLoad];

[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(onNotice)name:@"NoticeIdentifier"object:nil];

[selfaddObserver:targetforKeyPath:@"keyPath"options:NSKeyValueObservingOptionNewcontext:nil];

}

-(void)dealloc{

[[NSNotificationCenterdefaultCenter]removeObserver:selfname:@"NoticeIdentifier"object:nil];

[selfremoveObserver:targetforKeyPath:@"keyPath"];

}

隨著時間的積累,你會非常習慣這種寫法,并且蘋果也是這樣推薦的。但是慢慢你會發現所有對象的Dealloc函數都只做了這一件事,能不能不做這件事?FBKVOController也許會是一個不錯的選擇,Demo可以這樣寫

[self.KVOControllerobserve:clockkeyPath:@"date"options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNewblock:^(ClockView*clockView,Clock*clock,NSDictionary*change){

clockView.date=change[NSKeyValueChangeNewKey];

}];

FBKVOController的實現原理可以查看這篇文章,通過自釋放的實現,程序猿不再關心remove監聽。但是其還是有一定的局限性——對象無法監聽自己的屬性,如果你的代碼是這樣的

[self.KVOControllerobserve:selfkeyPath:@"date"options:NSKeyValueObservingOptionNewblock:^(NSDictionary*change){

// to do

}];

很遺憾,循環引用的問題又出現,因為FBKVOController中的NSMapTable對象會retain key對象,具體代碼如下

[_objectInfosMapsetObject:infosforKey:object];

那么,FBKVOController是如何做到自釋放的?可以歸納為四個字——動態屬性。其為觀察者綁定動態屬性self.KVOController,動態綁定的KVOController會隨著觀察者的釋放而釋放,KVOController在自己的dealloc函數中移除KVO監聽,巧妙的將觀察者的remove轉移到其動態屬性的dealloc函數中。

可是,這又有什么用?對象仍然無法監聽自己的屬性,還是要重寫set函數。HTBKVObservation也許會改變你的想法,其和FBKVOController來自同一人,代碼可以這樣寫

self.anObservation=[HTBKVObservationobserve:anObjectToObservekeyPath:@"observeMe"options:0callback:^(HTBKVObservation*observation,NSDictionary*changeDictionary){

// to do

}];

HTBKVObservation并沒用采用動態屬性,而是采用屬性的方式實現自釋放。可以監控對象自己的屬性,但是需要創建屬性HTBKVObservation。這里我對其做了一點擴展,方便使用,代碼可以這樣寫[selfobserve:selfkeyPath:@"KVOPath"options:NSKeyValueObservingOptionNewcallback:^(HTBKVObservation*observation,NSDictionary*changeDictionary){

// to do

}];

FBKVOControllerHTBKVObservation通過屬性或動態屬性巧妙的將KVO的remove轉移給第三者,實現了KVO事件的解耦,為自釋放的實現提供了一種借鑒思路

NSNotification的自釋放

談完 KVO,再來談談NSNotification。針對Notification,ReactiveCocoa做了很好的封裝,網上有很多介紹其如何使用的文章,在此不再累述。直接看代碼

[[[NSNotificationCenterdefaultCenter]rac_addObserverForName:UIKeyboardDidChangeFrameNotificationobject:nil]subscribeNext:^(idx){

// to do

}

];

簡單明了,當觀察者dealloc,很遺憾,NSNotification并沒用移除,因為對象并沒用自釋放,正確代碼應該是這樣

1

2

3

4[[[[NSNotificationCenterdefaultCenter]rac_addObserverForName:UIKeyboardDidChangeFrameNotificationobject:nil]takeUntil:self.rac_willDeallocSignal]subscribeNext:^(idx){

// to do

}

];

ReactiveCocoa自釋放的原理與FBKVOController不同,其并不是通過屬性或者動態屬性的方式實現,而是通過swizzling觀察對象的dealloc函數,在自定義dealloc函數實施清理,但不是默認清理,需要我們告訴它willDeallocSignal的時候完成所有清理工作。

除了定時器、KVO、NSNotification,包括封裝的某個功能對象,比如HttpRequest,或者數據庫ListSql等,合理的利用自釋放可以給使用者帶來更多的便利,同時也會減少 crash 產生的概率。實現自釋放的方法可以總結為以下三種方式

動態屬性的自釋放

@property 的自釋放

swizzling dealloc的自釋放

可以根據具體業務或者設計思想選擇對應的實現方式,這是一種思想,更是一個習慣。相信你會愛上它,畢竟誰不喜歡簡潔的實現方式了!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容