NSTimer
NSTimer 常用的兩種創建方式
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
第一種方式
通過 timerWith… 方法創建的 timer ,需要手動添加到 runloop 中,否則不會啟動。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
第二種方式
通過 scheduleTimerWith… 方法創建的 timer,會默認被添加到 runloop 的NSDefaultRunLoopMode
中,我們可以手動修改 runloop 的模式。
NSTimer *timer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
timer不準的原因
模式的改變
主線程的 runloop 里有兩個預置的Mode:NSDefaultRunLoopMode
和 UITrackingRunLoopMode
。
當創建一個 timer 并添加到 defaultMode 時,timer會得到重復回調。但此時滑動一個scrollview,RunLoop 會將 mode 切換到 UITrackingRunLoopMode,這時 timer 不會被回調,并且也不會影響到滑動操作。所以會導致NSTimer不準的情況。
解決方案:
定時器添加到 runloop 的 NSRunLoopCommonModes
模式中,該模式是以上兩種模式的組合。
線程阻塞
timer 觸發時,如果 runloop 在阻塞狀態,timer 的觸發會推遲到下一個 runloop 周期,導致延遲。
dispatch_source 實現高精度定時器
GCD 實現的定時器不受 runloop 模式的影響,使用 dispatch_source 能夠實現沒有延遲的定時器。
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue1);
self.timer = timer;
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_resume(timer);
CADisplayLink
能保證和屏幕刷新率相同的頻率,將特定的內容畫到屏幕上。
CADisplayLink 以特定的模式的添加到 runloop 后,每當屏幕需要刷新時,runloop 就會像 target 發送 selector 消息。所以 displayLink 的時間間隔是和屏幕刷新頻率相關聯的。
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];