iOS NSTimer內(nèi)存問題分析及解決方案

NSTimer是我們經(jīng)常使用的一個跟時間相關的oc類,作用是在指定的時間觸發(fā)對應的事件。

一、使用NSTimer

在iOS10.0之前有:

- (instancetype)initWithFireDate:(NSDate*)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullableid)ui repeats:(BOOL)rep;

+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;

我們試試 init構造方法:

init構造方法

結果發(fā)現(xiàn)日志竟然沒有打印,what?

試試timer開頭的構造方法:

timer構造方法

結果發(fā)現(xiàn)日志同樣沒有打印,what?

試試schedule開頭的方法,會不會有奇跡出現(xiàn):


schedule構造方法

奇跡出現(xiàn)了,what?Are you ok?好慌^_^,嚇得我趕緊看了看蘋果爸爸怎么說的。

Timers work in conjunction with run loops. Run loops maintain strong references to their timers.Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated.?

蘋果爸爸說

大致的意思是timer是工作在runloop中,并被runloop強引用,只有加入runloop才能生效呢,然后蘋果爸爸又說了,init跟timer開頭的沒有加入到任何runloop中,schedule開頭的默認加入了當前的runloop中,所以就出現(xiàn)了上述的情況。

好了趕快試試:

試試

果然就是這樣的,蘋果爸爸就是折騰人,干嘛不一樣處理呢?俗話說,想滅之,必先予之,哦哦,玩笑,蘋果爸爸肯定不是這樣想,可能出于開發(fā)者的角度,更多的自主權。

二、NSTimer準確么?

答案是不準的,有兩點需要說明:

1、iOS7.0后,timer加入runloop后,并不是準確的在指定的時間點觸發(fā)事件,是有一個容忍度的,大概在10%左右,蘋果爸爸的原話如下:In iOS?7 and later and macOS?10.9 and later, you can specify a tolerance for a timer (tolerance). This flexibility in when a timer fires improves the system's ability to optimize for increased power savings and responsiveness. The timer may fire at any time between its scheduled fire date and the scheduled fire date plus the tolerance. The timer doesn't fire before the scheduled fire date. For repeating timers, the next fire date is calculated from the original fire date regardless of tolerance applied at individual fire times, to avoid drift. The default value is zero, which means no additional tolerance is applied. The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of the?tolerance?property.

As the user of the timer, you can determine the appropriate tolerance for a timer. A general rule, set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance has significant positive impact on the power usage of your application. The system may enforce a maximum value for the tolerance.

2、runloop分5中mode,其中三種模式是開發(fā)者可以接觸的,NSRunLoopCommonModes、NSDefaultRunLoopMode、UITrackingRunLoopMode。timer加入某種mode,只有會在對應的mode中觸發(fā)事件,如果加入NSDefaultRunLoopMode,那么當runloop切換到UITrackingRunLoopMode(例如滑動UIScrollView)下,那么timer就會停止,直到切回NSDefaultRunLoopMode中,timer才會繼續(xù)fire。

所以說,如果需要精準的觸發(fā)事件,那么建議通過GCD timer,因為CGD timer是通過內(nèi)核port觸發(fā)runloop的機制,就能夠達到精準制導!

三、關于內(nèi)存泄露

1、內(nèi)存泄漏分析:

什么,what?這個東西還有內(nèi)存泄漏,不是ARC么?Are you ok?

是的,沒錯,的確有內(nèi)存泄漏的風險。上面蘋果爸爸的文檔里就說了,timer會被runloop強引用,直到被invalidate,他們關系是這樣的:

關系圖

根據(jù)上面的關系圖,如果在target的dealloc方法里面invalidate掉timer,就會形成相互等待,造成循環(huán)引用,如下圖:

內(nèi)存泄漏的寫法

2、內(nèi)存泄漏解決方案:

這簡單,不再dealloc里面invalidate,在其他的時機去做這步,這樣當然沒問題,給你點個贊^?_?^,但是有些需求我們就需要在整個生命周期內(nèi)都需要維持timer,只能在dealloc里面走這步,怎么辦?還是從關系圖分析開始:

權威認證通過的解決方案

有些同學可能有疑問,既然是timer強引用target,我給他一個弱引用給他怎么樣?是的,你想得有道理,但是,即使你傳一個經(jīng)過__weak修飾得self給timer,依舊然并卵,timer依舊會是強引用了target,不知道蘋果爸爸內(nèi)部對他做了什么手腳,已經(jīng)哭暈在廁所,??,就是下圖的方式依舊不行:

__weak沒法打斷強引用

2.1、第三方作為target:

其實構造方法里面還有一種方式?jīng)]用:+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation*)invocation repeats:(BOOL)yesOrNo;

invocation構造方法

親測依舊沒有打斷強引用,但是給我們提供了一種思路,那就是第三方,oc有一個類開發(fā)基本上沒用過,就是虛基類NSProxy:

NSProxy

大致意思就是用來轉(zhuǎn)發(fā)消息用的。

NSProxy

子類需要重寫兩個方法:forwardInvocation:?和methodSignatureForSelector:?。具體用法參考NSProxy的簡單使用,好了不多說,直接擼代碼:

TimerProxy
TimerProxy
TestViewController

現(xiàn)在讓我們看看類圖結構:

加入第三方,打斷循環(huán)引用

2.2、block打斷:

不多說,直接上代碼:

NSTimer分類.h
NSTimer分類.m
block驗證

其實呢蘋果爸爸在iOS10.0以后,也意識到這個問題,這么簡單的問題非得讓開發(fā)者折騰,好吧,這個問題,我來解決,所以就有了iOS10.0的新版本API,新增了對應的block版本的構造方法:

+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

- (instancetype)initWithFireDate:(NSDate*)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))blockAPI_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

好了,就寫到這了,NSTimer就這些,歡迎指正?。?!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容