浅析NSTimer CADisplayLink内存泄露
偶得前言
本篇文章中我們主要談談NSTimer\CADisplayLink在使用過程中牽扯到內存泄露的相關問題及解決思路(文章末尾會附上Demo),有時候我們在不知情的情況容易入坑,最關鍵你還不知道自己掉坑了,閑話不多說,讓我們開始進入正題。
NSRunLoop與定時器
我們先來回顧一下NSRunLoop對NSTimer\CADisplayLink的影響。(為了方便,以下統稱定時器)
大家都知道定時器的運行需要結合一個NSRunLoop(有疑惑的同學可以查看Xcode Document,此處不細說),同時NSRunLoop對該定時器會有一個強引用,這也是為什么我們不對NSRunLoop中的定時器進行強引的原因(如:self.timer = timer, 此代碼可省略)。
- invalidate的作用
由于NSRunLoop對定時器有著牽引,那么問題就來了,那么定時器怎樣才能被釋放掉呢(先不考慮使用removeFromRunLoop:),此時- invalidate函數的作用就來了,我們來看看官方就此函數的介紹:
Removes the object from all runloop modes (releasing the receiver if it has been implicitly retained) and releases the 'target' object.
據官方介紹可知,- invalidate做了兩件事,首先是把本身(定時器)從NSRunLoop中移除,然后就是釋放對‘target’對象的強引用。從而解決定時器帶來的內存泄露問題。
內存泄露在哪?
看到這里我們可能會有點懵逼,先上一個圖(為了方便講解,途中箭頭指向誰就代表強引誰):
此處我們必須明確,在開發中,如果創建定時器只是簡單的計時,不做其他引用,那么timer對象與myClock對象循環引用的問題就可以避免(即省略self.timer = timer,前文已經提到過,不再闡述),即圖中箭頭5可避免。
雖然孤島問題已經避免了,但還是存在問題,因為myClock對象被UIViewController以及timer引用(timer直接被NSRunLoop強引用著),當UIViewController控制器被UIWindow釋放后,myClock不會被銷毀,從而導致內存泄露。
講到這里,有些人可能會說對timer對象發送一個invalidate消息,這樣NSRunLoop即不會對timer進行強引,同時timer也會釋放對myClock對象的強引,這樣不就解決了嗎?沒錯,內存泄露是解決了。
但是,這并不是我們想要的結果,在開發中我們可能會遇到某些需求,只有在myClock對象要被釋放時才去釋放timer(此處要注意釋放的先后順序及釋放條件),如果提前向timer發送了invalidate消息,那么myClock對象可能會因為timer被提前釋放而導致數據錯了,就像鬧鐘失去了秒針一樣,就無法正常工作了。所以我們要做的是在向myClock對象發送dealloc消息前在給timer發送invalidate消息,從而避免本末倒置的問題。這種情況就像一個死循環(因為如果不給timer發送invalidate消息,myClock對象根本不會被銷毀,dealloc方法根本不會執行),那么該怎么做呢?
我們如何解決?
現在我們已經知道內存泄露在哪了,也知道原因是什么,那么如何解決,或者說怎樣優雅的解決這問題呢?方式有很多.
a.NSTimer Target
為了解決timer與myClock之間類似死鎖的問題,我們會將定時器中的‘target’對象替換成定時器自己,采用分類實現。
#import?"NSTimer+TXTimerTarget.h"@implementation?NSTimer?(TXTimerTarget)+?(NSTimer?*)tx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval?repeat:(BOOL)yesOrNo?block:(void?(^)(NSTimer?*))block{????return?[self?scheduledTimerWithTimeInterval:interval?target:self?selector:@selector(startTimer:)?userInfo:[block?copy]?repeats:yesOrNo]; }+?(void)startTimer:(NSTimer?*)timer?{????void?(^block)(NSTimer?*timer)?=?timer.userInfo;????if?(block)?{block(timer);} }@endb.NSTimer Proxy
這種方式就是創建一個NSProxy子類TXTimerProxy(不太清楚NSProxy的同學可以去查一下相關資料哈),TXTimerProxy的作用是什么呢?就是什么也不做,可以說只會重載消息轉發機制,如果創建一個TXTimerProxy對象將其作為timer的‘target’,專門用于轉發timer消息至myClock對象,那么問題是不是就解決了呢?答案:是的。
NSTimer?*timer?=?[NSTimer?scheduledTimerWithTimeInterval:0.25?target:[TXTimerProxy?timerProxyWithTarget:self]?selector:@selector(startTimer)?userInfo:nil?repeats:YES];[[NSRunLoop?currentRunLoop]?addTimer:timer?forMode:NSRunLoopCommonModes];self.timer?=?timer;實現詳情文章末尾會附上Demo,感興趣的同學可以去看看哈,有什么問題可以直接問,互相交流。
c.NSTimer Block
還有一種方式就是采用Block,iOS 10增加的API。
+?scheduledTimerWithTimeInterval:repeats:block:The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
有點類似a方式,此處不再詳述。
//NSTimer?Block(解決self內存泄露)?模擬器會崩潰//API_AVAILABLE(macosx(10.12),?ios(10.0),?watchos(3.0),?tvos(10.0));NSTimer?*timer?=?[NSTimer?scheduledTimerWithTimeInterval:0.25?repeats:YES?block:^(NSTimer?*?_Nonnull?timer)?{????NSLog(@"TXNSTimerBlockController?timer?start"); }];[[NSRunLoop?mainRunLoop]?addTimer:timer?forMode:NSRunLoopCommonModes];self.timer?=?timer;此處以NSTimer舉例,CADisplayLink不再詳述(方式都是一樣)。
本文轉自lzwxx 51CTO博客,原文鏈接:http://blog.51cto.com/13064681/1943387
總結
以上是生活随笔為你收集整理的浅析NSTimer CADisplayLink内存泄露的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hybrid框架UI重构之路:三、工欲善
- 下一篇: ngnix之lnmp环境搭建及Dvbbs