iOS NSTimer的問題總結(jié)

NSTimer在時(shí)長(zhǎng)開發(fā)中使用頻率還是比較高的,但一個(gè)不注意可能就會(huì)造成了小問題异旧,日常使用中還需多注意才是。

NSTimer計(jì)時(shí)準(zhǔn)確嗎

問:NSTimer計(jì)時(shí)準(zhǔn)確嗎?

答:NSTimer 如果如下使用一般不準(zhǔn)確:

//在主線程中調(diào)用
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(logInfo) userInfo:nil repeats:YES];

//或者 在主線程中調(diào)用
 _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(logInfo) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];

以上兩種方式直接調(diào)用NSTimer時(shí)很可能會(huì)計(jì)時(shí)不準(zhǔn)確右遭,原因如下:

  1. NSTimer在每一次的runloop中會(huì)被處理,但當(dāng)runloop中有其他比較多的耗時(shí)操作缤削,且操作時(shí)間超過了NSTimer的間隔窘哈,那么這一次的NSTimer就會(huì)被延后處理。導(dǎo)致不準(zhǔn)確亭敢。
    解決辦法:可以將NSTimer放入子線程滚婉,并手動(dòng)開啟子線程的runloop。當(dāng)前runloop中沒有其他耗時(shí)操作帅刀,所以也會(huì)相對(duì)準(zhǔn)確一些让腹。

  2. runloop model 模式的影響,當(dāng)沒有指定時(shí)扣溺,runloop默認(rèn)會(huì)添加到RunLoopDefaultMode中骇窍,當(dāng)頁(yè)面有tableview滑動(dòng)時(shí),主線程的runloop會(huì)切換到TrackingRunLoopMode锥余,此模式下像鸡,NSTimer不會(huì)被觸發(fā),導(dǎo)致計(jì)時(shí)不準(zhǔn)確哈恰。

NSTimer 內(nèi)存泄漏

NSTimer涉及到內(nèi)存泄漏只估,主要是指在Timer中repeatsYES,即需要間隔指定時(shí)間着绷,重復(fù)調(diào)用方法蛔钙,此時(shí)需要手動(dòng)調(diào)用[self.timer invalidate]使定時(shí)器失效。

如果repeatsNO荠医,則不存在內(nèi)存泄漏問題吁脱,調(diào)用完成后桑涎,定時(shí)器會(huì)自動(dòng)失效。

問題引發(fā):

一般使用NSTimer時(shí)兼贡,用法如下:

self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

上面代碼這樣寫攻冷,定時(shí)器會(huì)正常調(diào)用,但當(dāng)退出當(dāng)前頁(yè)面或者功能之后遍希,定時(shí)器依然在調(diào)用等曼,并沒有停止。想要停止凿蒜,需要手動(dòng)調(diào)用[self.timer invalidate]方法禁谦。

可能想到下面這種寫法:

- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
}

這種寫法咋一看沒有問題,不是要手動(dòng)調(diào)用停止定時(shí)器的方法嘛废封,那在頁(yè)面釋放的時(shí)候州泊,調(diào)用就好了。

經(jīng)過測(cè)試漂洋,可以發(fā)現(xiàn)這樣寫的話遥皂,當(dāng)前對(duì)象self不會(huì)釋放,定時(shí)器也沒有釋放刽漂,這就造成了內(nèi)存泄漏渴肉。

分析問題:

再看一下之前的代碼:

- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
}

self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

self持有了timer, 而timertarget參數(shù)里持有了self

相互引用

這就導(dǎo)致了相互引用。

可能有的同學(xué)立馬就有了疑惑:

  1. self不持有timer也可以正常使用爽冕,為什么要持有
  2. timer為什么會(huì)持有self

針對(duì)以上兩個(gè)問題答疑:

  1. self持有timer是因?yàn)樾枰谄渌牡胤绞謩?dòng)調(diào)用[self.timer invalidate]仇祭,self不持有timer確實(shí)可以簡(jiǎn)單解決相互引用問題,但timer卻無法手動(dòng)釋放颈畸。
  2. runloop需要對(duì)timer的觀察者做保留操作乌奇,以便后續(xù)指定的時(shí)間點(diǎn)來到時(shí)做指定操作。
解決問題

解決上面的內(nèi)存泄漏問題眯娱,大致有下面幾種辦法:

  1. viewWillDisappear:手動(dòng)停止
  2. 重寫返回方法手動(dòng)停止
  3. iOS10之后礁苗,系統(tǒng)提供的scheduledTimerWithTimeInterval:repeats:block方法
  4. 去除NSTimer和當(dāng)前調(diào)用對(duì)象之間的循環(huán)引用。

詳細(xì)解釋:

手動(dòng)停止定時(shí)器徙缴,要有一個(gè)時(shí)機(jī)试伙。如果根據(jù)需求,剛好可以在用戶操作某項(xiàng)功能時(shí)可以主動(dòng)停止于样,算是一個(gè)比較好的辦法疏叨。但如果沒有這個(gè)時(shí)機(jī)則可以在1、2兩個(gè)方法中解決穿剖。

  1. viewWillDisappear:手動(dòng)停止

    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [self.timer invalidate];
        self.timer = nil;
    }
    

    需要注意在這個(gè)方法中停止蚤蔓,適用于不會(huì)從當(dāng)前頁(yè)面push出下一個(gè)頁(yè)面的情況,因?yàn)槿绻鹥ush出下一個(gè)頁(yè)面糊余,也會(huì)調(diào)用viewWillDisappear:秀又,可能就得不到正確的結(jié)果单寂。

  2. 重寫返回方法手動(dòng)停止

    - (void)backButtonPressed:(id)sender {
        [self.timer invalidate];
        self.timer = nil;
    }
    
  3. 蘋果估計(jì)也發(fā)現(xiàn)了timer容易導(dǎo)致內(nèi)存泄漏的問題,所以在iOS10之后吐辙,出了一個(gè)新的API宣决,使用新API是沒有內(nèi)存泄漏的。

    if (@available(iOS 10.0, *)) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"do more thing");
        }];
    }
    
    - (void)dealloc
    {
        [self.timer invalidate];
        self.timer = nil;
    }
    

    調(diào)用這個(gè)新的API則不會(huì)相互強(qiáng)引用昏苏,dealloc方法會(huì)正常調(diào)用尊沸。

  4. 去除NSTimer和當(dāng)前調(diào)用對(duì)象之間的循環(huán)引用。
    從3中的系統(tǒng)方法其實(shí)可以得到啟發(fā)捷雕,3中的方法里并沒有直接引用self椒丧,而是讓timer引用了其他的對(duì)象壹甥,這樣就解除了相互引用救巷。
    對(duì)于iOS10一下的系統(tǒng),可以模仿一下系統(tǒng)的實(shí)現(xiàn)句柠。

    #import <Foundation/Foundation.h>
    
     NS_ASSUME_NONNULL_BEGIN
    
     @interface NSTimer (weak)
    
     + (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void(^)(NSTimer *timer))block;
    
     @end
    
     NS_ASSUME_NONNULL_END
    
    #import "NSTimer+weak.h"
    
    @implementation NSTimer (weak)
    
    + (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{
         return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(weak_blcokInvoke:) userInfo:[block copy] repeats:repeats];
    }
    
    + (void)weak_blcokInvoke:(NSTimer *)timer {
     
        void (^block)(NSTimer *timer) = timer.userInfo;
     
         if (block) {
             block(timer);
         }
     }
    
     @end
    

    在使用時(shí)浦译,可以如下:

    self.timer = [NSTimer weak_scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
         //do some thing
    }];
     
    - (void)dealloc
     {
         [self.timer invalidate];
         self.timer = nil;
     }
    

    可以看把target由之前原始的vc對(duì)象,轉(zhuǎn)換成了timer對(duì)象溯职,從而打破了雙向引用精盅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谜酒,隨后出現(xiàn)的幾起案子叹俏,更是在濱河造成了極大的恐慌,老刑警劉巖僻族,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粘驰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡述么,警方通過查閱死者的電腦和手機(jī)蝌数,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來度秘,“玉大人顶伞,你說我怎么就攤上這事〗J幔” “怎么了唆貌?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)垢乙。 經(jīng)常有香客問我挠锥,道長(zhǎng),這世上最難降的妖魔是什么侨赡? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任蓖租,我火速辦了婚禮粱侣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蓖宦。我一直安慰自己齐婴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布稠茂。 她就那樣靜靜地躺著柠偶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪睬关。 梳的紋絲不亂的頭發(fā)上诱担,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音电爹,去河邊找鬼蔫仙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛丐箩,可吹牛的內(nèi)容都是我干的摇邦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼屎勘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼施籍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起概漱,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤丑慎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瓤摧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竿裂,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年姻灶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铛绰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡产喉,死狀恐怖捂掰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情曾沈,我是刑警寧澤这嚣,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站塞俱,受9級(jí)特大地震影響姐帚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜障涯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一罐旗、第九天 我趴在偏房一處隱蔽的房頂上張望膳汪。 院中可真熱鬧,春花似錦九秀、人聲如沸遗嗽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)痹换。三九已至,卻和暖如春都弹,著一層夾襖步出監(jiān)牢的瞬間娇豫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工畅厢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冯痢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓或详,卻偏偏與公主長(zhǎng)得像系羞,于是被迫代替她去往敵國(guó)和親郭计。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霸琴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容