一、介紹
在iOS中,計時器是比較常用的,用於統計累加數據或者倒計時等,例如手機號獲取驗證碼。計時器大概有那么三種,分別是:NSTimer、CADisplayLink、dispatch_source_t
二、使用
@property (strong,nonatomic)NSTimer *timer; @property (strong,nonatomic)CADisplayLink *displaylinkTimer; @property (strong,nonatomic)dispatch_source_t sourceTimer;
1、NSTimer:
//NSTimer -(void)createTimer{ //初始化 //_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { //執行操作 //}]; _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerStart:) userInfo:nil repeats:YES]; //加入runloop循環池 [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode]; //開啟定時器 [_timer fire]; }
-(void)timerStart:(NSTimer *)timer{ NSLog(@"%s-----%lf",__func__,timer.timeInterval); //銷毀定時器 //[_timer invalidate]; //_timer = nil; }
解釋:
- TimerInterval: 執行之前等待的時間。比如設置成1.0,就代表1秒后執行方法
- target: 需要執行方法的對象。
- selector : 需要執行的方法
- repeats : 是否需要循環
注意 :
調用創建方法后,target
對象的計數器會加1,直到執行完畢,自動減1。如果是循環執行的話,就必須手動關閉,否則可以不執行釋放方法。
特性:
- 存在延遲
- 不管是一次性的還是周期性的timer的實際觸發事件的時間,都會與所加入的RunLoop和RunLoop Mode有關,如果此RunLoop正在執行一個連續性的運算,timer就會被延時出發。重復性的timer遇到這種情況,如果延遲超過了一個周期,則會在延時結束后立刻執行,並按照之前指定的周期繼續執行。
- 必須加入Runloop
使用上面的創建方式,會自動把timer加入MainRunloop的NSDefaultRunLoopMode中。如果使用以下方式創建定時器,就必須手動加入Runloop:
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
2、CADisplayLink:
//CADisplayLink -(void)createCADisplayLink{ _displaylinkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; [_displaylinkTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; }
-(void)handleDisplayLink:(CADisplayLink *)displaylinkTimer{ NSLog(@"%s-----%ld",__func__,displaylinkTimer.preferredFramesPerSecond); //銷毀定時器 //[_displaylinkTimer invalidate]; //_displaylinkTimer = nil; }
解釋:
當把CADisplayLink對象add到runloop中后,selector就能被周期性調用,類似於重復的NSTimer被啟動了;執行invalidate操作時,CADisplayLink對象就會從runloop中移除,selector調用也隨即停止,類似於NSTimer的invalidate方法。
特性:
- 屏幕刷新時調用CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內容畫到屏幕上的定時器類。CADisplayLink以特定模式注冊到runloop后,每當屏幕顯示內容刷新結束的時候,runloop就會向CADisplayLink指定的target發送一次指定的selector消息, CADisplayLink類對應的selector就會被調用一次。所以通常情況下,按照iOS設備屏幕的刷新率60次/秒
- 延遲iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結束都被調用,精確度相當高。但如果調用的方法比較耗時,超過了屏幕刷新周期,就會導致跳過若干次回調調用機會。如果CPU過於繁忙,無法保證屏幕60次/秒的刷新率,就會導致跳過若干次調用回調方法的機會,跳過次數取決CPU的忙碌程度。
使用場景:
- 從原理上可以看出,CADisplayLink適合做界面的不停重繪,比如視頻播放的時候需要不停地獲取下一幀用於界面渲染。
重要屬性:
- frameInterval(已過時,用preferredFramesPerSecond替代) NSInteger類型的值,用來設置間隔多少幀調用一次selector方法,默認值是1,即每幀都調用一次。
- duration readOnly的CFTimeInterval值,表示兩次屏幕刷新之間的時間間隔。需要注意的是,該屬性在target的selector被首次調用以后才會被賦值。selector的調用間隔時間計算方式是:調用間隔時間 = duration × frameInterval。
3、dispatch_source_t:
//dispatch_source_t -(void)createDispatch_source_t{ //創建全局隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //使用全局隊列創建計時器 _sourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); //定時器延遲時間 NSTimeInterval delayTime = 1.0f; //定時器間隔時間 NSTimeInterval timeInterval = 1.0f; //設置開始時間 dispatch_time_t startDelayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)); //設置計時器 dispatch_source_set_timer(_sourceTimer,startDelayTime,timeInterval*NSEC_PER_SEC,0.1*NSEC_PER_SEC); //執行事件 dispatch_source_set_event_handler(_sourceTimer,^{ //銷毀定時器 //dispatch_source_cancel(_myTimer); }); //啟動計時器 dispatch_resume(_sourceTimer); }
特性:
- 默認是重復執行的,可以在事件響應回調中通過dispatch_source_cancel方法來設置為只執行一次,如下代碼:
dispatch_source_set_event_handler(_timer, ^{ //執行事件 dispatch_source_cancel(_timer);} );
重要屬性:
dispatch_source_set_timer(dispatch_source_t source,
dispatch_time_t start,
uint64_t interval,
uint64_t leeway);
- start計時器起始時間,可以通過dispatch_time創建,如果使用DISPATCH_TIME_NOW,則創建后立即執行
- interval計時器間隔時間,可以通過timeInterval * NSEC_PER_SEC來設置,其中,timeInterval為對應的秒數
- leeway這個參數的理解,我覺得http://www.dreamingwish.com上Seven's同學的解釋很直觀也很易懂:“這個參數告訴系統我們需要計時器觸發的精准程度。所有的計時器都不會保證100%精准,這個參數用來告訴系統你希望系統保證精准的努力程度。如果你希望一個計時器沒五秒觸發一次,並且越准越好,那么你傳遞0為參數。另外,如果是一個周期性任務,比如檢查email,那么你會希望每十分鍾檢查一次,但是不用那么精准。所以你可以傳入60,告訴系統60秒的誤差是可接受的。這樣有什么意義呢?簡單來說,就是降低資源消耗。如果系統可以讓cpu休息足夠長的時間,並在每次醒來的時候執行一個任務集合,而不是不斷的醒來睡去以執行任務,那么系統會更高效。如果傳入一個比較大的leeway給你的計時器,意味着你允許系統拖延你的計時器來將計時器任務與其他任務聯合起來一起執行。
優點:
- 時間准確
- 可以使用子線程,解決定時間跑在主線程上卡UI問題
注意事項:
- 需要將dispatch_source_t timer設置為成員變量,不然會立即釋放
三、參考原鏈接: