關於定時器

weixin_34236869發表於2017-10-02
1.NSTimer的建立
  • 常用方法一:該方法內部自動新增到runloop中, 執行模式為預設
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

// 如果在子執行緒中呼叫改方法,不會執行,因為子執行緒runloop必須手動開啟:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:@{@"key":@"value"} repeats:true]; 
// 子執行緒runloop手動開啟
[[NSRunLoop currentRunLoop] run]; 
});
  • 不常用方法二:該方法建立的NSTimer必須手動新增到runloop中
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[runloop addTimer:timer forMode:NSDefaultRunLoopMode];
  • 不常用方法三:兩個方法建立的NSTimer必須手動新增到runloop中
- (instancetype)initWithFireDate: interval: target: selector: userInfo: repeats:
- (void)initWithFireDate: interval: repeats: block:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
2.NSTimer與Runloop:
  • 為什麼NSTimer要新增到Runloop:
    • NSTimer是一種source,所以的source要起作用,都要新增到runloop中
  • 如果新增到了kCFRunLoopDefaultMode模式中,在拖動介面的時候,runloop模式只能唯一,這時切換為UITrackingRunLoopMode,定時器停止工作
  • 如果新增到了kCFRunLoopCommonModes模式中,任何runloop模式都可以工作了
    • Runloop的三種模式:
// 預設模式
kCFRunLoopDefaultMode
// 拖動模式
UITrackingRunLoopMode
// 此模式包含了以上兩種模式
kCFRunLoopCommonModes
3.NSTimer的迴圈引用
  • 建立NSTimer時,最後一個引數如果為YES,那麼NSTimer對於target:self形成強引用;而self因為後續要用到定時器,所以對於定時器也形成了強引用;這就形成了迴圈引用
  • 如果我們啟動了一個NSTimer,在某個介面釋放前,將這個NSTimer停止,甚至置為nil,都不能使這個介面釋放,原因是系統的迴圈池中還保有這個物件
4.如何解決迴圈引用

4.1.控制器不再引用NSTimer
4.2.NSTimer不再引用控制器

  • fire方法
    • 啟動 Timer, 觸發Target的方法呼叫但是並不會改變Timer的時間設定。 如果定時器的方法是non-repeating,呼叫之後自動銷燬
- (void)fire;
  • 銷燬NSTimer的唯一辦法:(nstimer的建立和銷燬要在一個runloop中)
// 要在dealloc方法呼叫之前銷燬NSTimer
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    if (timer.isValid) {
    // 從runloop中銷燬,銷燬系統對於NSTimer的強引用
    [_timer invalidate];
    // 將屬性置空銷燬,銷燬self對於NSTimer的強引用
    _timer = nil;
}
}
.5.GCD定時器
  • GCD 絕對精準,不受runloop影響
#import "ViewController.h"

@interface ViewController ()
/** 定時器屬性 */
@property (nonatomic, strong) dispatch_source_t timer;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //1.建立GCD中的定時器
    /*
     第一個引數: source的型別DISPATCH_SOURCE_TYPE_TIMER 表示是定時器
     第二個引數: 描述資訊,執行緒ID
     第三個引數: 更詳細的描述資訊
     第四個引數: 佇列,決定GCD定時器中的任務在哪個執行緒中執行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    //2.設定定時器(起始時間|間隔時間|精準度)
    /*
     第一個引數:定時器物件
     第二個引數:起始時間,DISPATCH_TIME_NOW 從現在開始計時
     第三個引數:間隔時間 2.0 GCD中時間單位為納秒
     第四個引數:精準度 絕對精準0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    //3.設定定時器執行的任務
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD---%@",[NSThread currentThread]);
    });
    
    //4.啟動執行
    dispatch_resume(timer);
    
    self.timer = timer;
}
@end

6.CADisplayLink定時器

  • 建立CADisplayLink定時器
self.displayLink = [CADisplayLink displayLinkWithTarget:self sel:ector:@selector(handleDisplaylink:)]; 
[self.displayLink addToRunLoop:[NSRunLoop curentRunLoop] forMode:NSDefaultRunLoopMode];
  • 停止CADisplayLink定時器
[self.displayLink invailidate];
self.displayLink = nil;
  • 把CADisplayLink新增到runloop中後,方法就會被週期性呼叫。invailidate後,CADisplayLink從runloop中移除,方法停止呼叫

  • 使用場景:

    • 以螢幕的重新整理頻率執行某操作(重新整理頻率通常為60次/秒)
    • 介面重繪,比如視訊播放
  • CADisplayLink延遲問題

    • 如果執行的操作比較費時,會出現跳過執行操作的情況

相關文章