NSTimer在使用中需要注意的點

NSTimer的常用API
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
  Initializes a timer object with the specified object and selector.
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
  Creates a timer and schedules it on the current run loop in the default mode.

根據官方文檔方法一和方法二的區別在于:使用方法一創建的Timer不會添加到NSRunLoop需要手動添加;使用方法二會自動添加到主線程的RunLoop中。

- (void)fire; 
  Causes the timer's message to be sent to its target.
- (void)invalidate;
  Stops the timer from ever firing again and requests its removal from its run loop.

fire方法可以立即觸發Timer對象的target方法;invalidate會停止Timer并將其從runloop中移除

NSRunLoop & NSTimer

當使用NSTimerscheduledTimerWithTimeInterval方法時,NSTimer的實例會被加入到當前線程的RunLoop中,模式為默認模式NSDefaultRunLoopMode

- (void)viewDidLoad
{
    [super viewDidLoad];
        
    NSLog(@"主線程 %@", [NSThread currentThread]);
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
    //使用NSRunLoopCommonModes模式,把timer加入到當前Run Loop中。
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)doSomething
{
    NSLog(@"Timer %@", [NSThread currentThread]);
}

控制臺輸出結果:上下打印出來的線程都是主線程。

如果當你線程是主線程也就是UI線程時,某些UI事件(UIScrollView的滑動操作),會將RunLoop切換到NSEventTrackingRunLoopMode模式。
在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到RunLoop中的Timer就不會執行。這是一個在開發中經常碰到的場景,如何解決這個問題?我們可以在創建完一個Timer后使用NSRunLoopaddTimer:forMode:方法,使用NSRunLoopCommonModes。這是一個占位用的Mode,可以在不同情況下扮演不同的角色——NSDefaultRunLoopMode & UITrackingRunLoopMode

NSTimer循環引用問題

NSTimer為什么會造成循環引用?
在開發中我們經常將Timer作為控制器的屬性來使用,這樣一來控制器對Timer進行了強引用。在target-action這個過程中,Timer又對self做了強引用,這就是導致循環引用的原因了。在網上有非常多的解決方案,我總結了一下有下面幾種。

  • 方法一:在viewWillDisappear中執行下面的操作。

    - (void)viewWillDisappear:(BOOL)animated {
      [super viewWillDisappear:animated];
    
      [_timer invalidate];
      _timer = nil;
    }
    
     -- 
     蘋果文檔中關于target強引用的解釋
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
      Parameters
      ti 
      The number of seconds between firings of the timer. If ti is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead.
      target 
      The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
    

The timer maintains a strong reference to this object until it (the timer) is invalidated.蘋果文檔說invalidate以后timer就不再保有對target的強引用了。所以解決循環引用的關鍵在與invalidate方法有沒有執行。下面_timer = nil這句話的意義是,invalidate方法執行以后Timer就不能復用了,為了防止在其之后其他地方再次使用Timer,在這里即使將其置為nil。

  • 方法二:引入一個代理對象,讓其弱引用self,參考YYWeakProxy
    沒引入代理(Proxy)之前是:self -> timer -> self這樣的循環引用。在引入代理(Proxy)之后是:self -> timer -> proxy ··> self。這樣一來就打破了之前的循環引用。

  • 方法三:使用YYTimerMSWeakTimer等GCD實現的Timer。
    我決定把這一部分內容拿出來單獨寫一篇對比GCD、CADisplayLink等計時器的總結,除了NSTimer還有兩種定時器:CADisplayLink & Dispatch Source Timer歡迎點贊和關注。

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

推薦閱讀更多精彩內容

  • 之前要做一個發送短信驗證碼的倒計時功能,打算用NSTimer來實現,做的過程中發現坑還是有不少的。 基本使用 NS...
    WeiHing閱讀 4,408評論 1 8
  • NSTimer是iOS最常用的定時器工具之一,在使用的時候常常會遇到各種各樣的問題,最常見的是內存泄漏,通常我們使...
    bomo閱讀 1,267評論 0 7
  • 要說軟萌之最,這個世界上大概唯有喵星人莫屬了吧,撒得了嬌,賣的了萌,分分鐘成為都市壓力之下的治愈系,讓主人們心甘情...
    泡面護膚搭檔閱讀 481評論 0 0
  • 深院靜,小庭空, 斷續寒砧斷續風。 小庭幽院清如水, 冰簾斜卷松針寒。 在繁華喧囂的都市一角,有這樣一處...
    嫣然66閱讀 481評論 0 4
  • 起風了,云很少,趕上了北京難得的藍天,忙碌的心開始閑下來,去細細品嘗秋光。 庭院的兩棵百年古樹在風中依偎搖曳,似在...
    木葉春城閱讀 344評論 0 0