在開發工作中,我們經常會用到NSTimer 來實現定時器功能。但用心留意的小伙伴兒應該注意到了一個問題:定時器受其他UI界面刷新或者手勢問題會出現卡頓現象,這會導致定時器不准確。究其原因:就是線程等待導致的問題。這也是面試中的一個經典案例。
具體原因:NSTimer 的runloop類型是NSDefaultRunloopMode 主線程中, 界面的刷新也在主線程中,UIScrollview滑動的過程中是在NSTrackingRunloopMode中,當我們在手指滑動過程中,系統會將NSDefaultRunloopMode 更改為NSTrackingRunloopMode,所以這也是為什么會出現NSTimer短暫暫停的原因
目前的解決方案有三種:
1.通過更改RunloopMode 避免主線程阻塞
因為要避免主線程阻塞 所以也就是要避免使用NSDefaultRunloopMode。
查看到runloopMode的類型,可以看到 NSCommonRunloopMode (偽模式)不常用,而且是作為一個通用存在的。所以我們只需將NSDefaultRunloopMode 改為NSCommonRunloopMode調用就可以了。
但有一點需要注意:如果有多個NSCommonRunloopMode 執行不同任務 還是會出現卡頓導致定時器不准確問題。
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
2.將NSTimer放在子線程中,當需要刷新UI界面時 再在主線程中進行。
這個方法是類似於將NSTimer 任務獨立出來,也就是將定時和刷新UI的工作任務分開來。
- (void)timerMethod2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
-(void)newThread
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
這個方法是可以實現但會增加代碼量,而且有一定的內存泄漏風險。
因為定時器的取消我們在delloc中進行的。如果對象沒有完全銷毀 就會導致內存泄漏問題出現。
所以比較推崇接下來的第三種方案。
3.gcd實現定時器
gcd完全不受runloop影響,也就是說不會收到手勢和UI刷新影響。所以它比NSTimer 更加准確。
int count = 0;
// 獲得隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 創建一個定時器(dispatch_source_t本質還是個OC對象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設置定時器的各種屬性(幾時開始任務,每隔多長時間執行一次)
// GCD的時間參數,一般是納秒(1秒 == 10的9次方納秒)
// 何時開始執行第一個任務
// dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比當前時間晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
// 設置回調
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
count++;
if (count == 4) {
// 取消定時器
dispatch_cancel(self.timer);
self.timer = nil;
}
});
// 啟動定時器
dispatch_resume(self.timer);