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];