- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0target:self selector:@selector(logInfo) userInfo:nil repeats:YES];
}
}
- (void)logInfo {
float count = 0;
for (int i = 0; i < 1000000000; i++) {
count += i;
}
NSLog(@"timer test");
}
2017-11-1009:40:38.194879+0800QTimer[9749:3330951] timer test
2017-11-1009:40:44.188463+0800QTimer[9749:3330951] timer test
2017-11-1009:40:50.172012+0800QTimer[9749:3330951] timer test
2017-11-1009:40:56.172139+0800QTimer[9749:3330951] timer test
2017-11-1009:41:02.179022+0800QTimer[9749:3330951] timer test
2017-11-1009:41:08.170254+0800QTimer[9749:3330951] timer test
2017-11-1009:41:14.169011+0800QTimer[9749:3330951] timer test
my god ... 计时偏差已经去到了6秒之多,显然是有问题的
原因分析:
定时器被添加在主线程中,由于定时器在一个RunLoop中被检测一次,所以如果在这一次的RunLoop中做了耗时的操作,当前RunLoop持续的时间超过了定时器的间隔时间,那么下一次定时就被延后了。
解决方法:
在子线程中创建timer,在主线程进行定时任务的操作
在子线程中创建timer,在子线程中进行定时任务的操作,需要UI操作时切换回主线程进行操作
2、RunLoop模式的影响
为了验证,我们在当前页面上添加一个tableview,在定时器运行时,我们对tableview进行滑动操作,可以发现,定时器并不会触发下一次的定时任务。
原因分析:
主线程的RunLoop有两种预设的模式,RunLoopDefaultMode和TrackingRunLoopMode。
当定时器被添加到主线程中且无指定模式时,会被默认添加到DefaultMode中,一般情况下定时器会正常触发定时任务。但是当用户进行UI交互操作时(比如滑动tableview),主线程会切换到TrackingRunLoopMode,在此模式下定时器并不会被触发。
解决方法:
添加定时器到主线程的CommonMode中或者子线程中
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
2、CADisplayLink
CADisplayLink是一个频率能达到屏幕刷新率的定时器类。iPhone屏幕刷新频率为60帧/秒,也就是说最小间隔可以达到1/60s。
基本使用:
CADisplayLink * displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
3、GCD定时器
我们知道,RunLoop是dispatch_source_t实现的timer,所以理论上来说,GCD定时器的精度比NSTimer只高不低。
基本使用:
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);