NSTimer使用注意的問題

總結(jié)一下平時(shí)使用NSTimer碰到的幾個(gè)小問題:

  • 作為定時(shí)器使用時(shí)不準(zhǔn)確的問題
  • ScrollView滾動(dòng)時(shí)timer不執(zhí)行的問題
  • 內(nèi)存泄漏的問題

NSTimer準(zhǔn)確性不高的原因

以上幾個(gè)問題都跟RunLoop有關(guān)系,一個(gè) NSTimer 注冊(cè)到RunLoop后,RunLoop 會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊(cè)好事件。例如 10:00, 10:10, 10:20 這幾個(gè)時(shí)間點(diǎn)。RunLoop為了節(jié)省資源,并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)Timer。Timer 有個(gè)屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時(shí)間點(diǎn)到后,容許有多少最大誤差。

如果某個(gè)時(shí)間點(diǎn)被錯(cuò)過了,例如執(zhí)行了一個(gè)很長(zhǎng)的任務(wù),則那個(gè)時(shí)間點(diǎn)的回調(diào)也會(huì)跳過去,不會(huì)延后執(zhí)行。就比如等公交,如果 10:10 時(shí)我忙著玩手機(jī)錯(cuò)過了那個(gè)點(diǎn)的公交,那我只能等 10:20 這一趟了。

ScrollView滾動(dòng)時(shí)timer不執(zhí)行的原因

這里有個(gè)概念叫 CommonModes:一個(gè) Mode 可以將自己標(biāo)記為Common屬性(通過將其 ModeName 添加到 RunLoopcommonModes 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop 都會(huì)自動(dòng)將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 Common 標(biāo)記的所有Mode里。

應(yīng)用場(chǎng)景舉例:主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為Common屬性。DefaultMode 是 App 平時(shí)所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào),但此時(shí)滑動(dòng)一個(gè)TableView時(shí),RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer 就不會(huì)被回調(diào),并且也不會(huì)影響到滑動(dòng)操作。

有時(shí)你需要一個(gè) Timer,在兩個(gè) Mode 中都能得到回調(diào),一種辦法就是將這個(gè) Timer 分別加入這兩個(gè) Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 commonModeItems 中。commonModeItems 被 RunLoop 自動(dòng)更新到所有具有Common屬性的 Mode 里去。

NSTimer導(dǎo)致內(nèi)存泄漏的原因及解決方法

原因
  • NSTimer作為局部變量使用,在添加到RunLoop中的時(shí)會(huì)被RunLoop強(qiáng)引用,導(dǎo)致Timer本身不能被釋放。
  • NSTimer作為屬性使用時(shí)只能聲明成強(qiáng)引用(使用弱引用添加到RunLoop中會(huì)導(dǎo)致奔潰),在創(chuàng)建timer的過程中,timer會(huì)強(qiáng)引用當(dāng)前添加的target,若target是當(dāng)前timer所屬的對(duì)象時(shí),會(huì)造成循環(huán)引用的問題
  • 當(dāng)timer釋放時(shí)所在線程跟timer添加到的RunLoop對(duì)應(yīng)的線程不一致時(shí)釋放會(huì)有問題
解決方法

在合適的時(shí)機(jī)調(diào)用invalidate方法。蘋果官方的描述如下:

This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

invaliate是唯一一個(gè)將timerNSRunLoop對(duì)象移除的方法,如果我們是使用targetuserinfo對(duì)象創(chuàng)建的,那么同樣會(huì)移除對(duì)這些對(duì)象強(qiáng)引用。

#import "TimerViewController.h"

@interface TimerViewController ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation TimerViewController

- (void)dealloc {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self propertyTimer];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    if ([self.timer isValid]) {
        [self.timer invalidate];
    }
}

#pragma mark - private method

- (void)propertyTimer {
    self.timer = [NSTimer timerWithTimeInterval:5
                                         target:self
                                       selector:@selector(propertyTimerMethod)
                                       userInfo:nil
                                        repeats:YES];
    
    [[NSRunLoop currentRunLoop] addTimer:self.timer
                                 forMode:NSRunLoopCommonModes];
    [self.timer fire];
}

- (void)propertyTimerMethod {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)localTimer {
    //局部變量 無法釋放
    NSTimer *timer = [NSTimer timerWithTimeInterval:5
                                             target:self
                                           selector:@selector(localTimerMethod)
                                           userInfo:nil
                                            repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer
                                 forMode:NSRunLoopCommonModes];
    [timer fire];
}

- (void)localTimerMethod {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

參考

深入理解RunLoop
iOS的幾種定時(shí)器及區(qū)別

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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