平常的開發中,咱們常常會用到計時器。在iOS
中,有三種計時器,NSTimer
、CADisplayLink
、dispatch_source
,這三種定時器都是各有優劣。多線程
NSTimer
NSTimer
是使用的比較多的一種,可是精度不夠,其緣由以下:函數
NSTimer
加在main runloop
中,模式是NSDefaultRunLoopMode
,main
負責全部主線程事務,例如UI
界面的操做、複雜的運算等等,這樣在同一個runloop
中NSTimer
就容易產生阻塞。NSTimer
的精度。主線程的runLoop
裏有兩個預置的mode
:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。當你建立一個NSTimer
並加到DefaultMode
時,NSTimer
會獲得重複回調,但此時若是滑動一個scrollView
時,runLoop
會將mode
切換爲TrackingRunLoopMode
,這時NSTimer
就不會被回調。因此就會影響到NSTimer
的精度。NSTimer
會強引用target
,而runLoop
會強持有NSTimer
,很容易出現內存泄漏。那麼如何讓NSTimer
精準一些?oop
NSTimer
實例加到main runloop
的特定mode
中,避免被複雜運算操做或者UI
界面刷新所影響。self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
複製代碼
NSTimer
的操做,而後在主線程中修改UI
界面顯示操做結果。- (void)timerMethod {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread {
@autoreleasepool
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
複製代碼
CADisplayLink
CADisplayLink
是一個能讓咱們以和屏幕刷新率同步的頻率將特定的內容畫到屏幕上的定時器類。CADisplayLink
以特定模式註冊到runloop
後, 每當屏幕顯示內容刷新結束的時候,runloop
就會向CADisplayLink
指定的target
發送一次消息,CADisplayLink
類對應的selector
就會被調用一次。post
iOS
設備的屏幕刷新頻率是固定的,CADisplayLink
在正常狀況下會在每次刷新結束都被調用,精確度至關高。CADisplayLink
使用場合相對專注,通常用於作UI
界面的不停重繪,好比自定義動畫引擎或者視頻播放的渲染。動畫
使用方式以下:spa
- (void)startDisplayLink {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)handleDisplayLink:(CADisplayLink *)link {
}
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
複製代碼
dispatch_source
dispatch_source_t
的定時器不受RunLoop
影響,並且dispatch_source_t
是系統級別的源事件,精度很高,系統自動觸發。線程
下面咱們就來定一個基於dispatch_source_t
的定時器。3d
首先,咱們知道dispatch_source_t
源事件有一種類型就是DISPATCH_SOURCE_TYPE_TIMER
,用來計時的。dispatch_source_t
的步驟以下:rest
另外咱們還須要在合適的地方設置源事件的數據。
其次,咱們知道,定義一個定時器,就須要運行、暫停、銷燬的功能。以及設置定時器定時的時間、是否重複、回調等配置。
另外,咱們要對外暴露實例方法、類方法來方便外界使用。
根據以上準備工做,咱們建立一個TGCDTimer
類。
@interface TGCDTimer : NSObject
/// 默認主線程建立
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block;
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block;
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block;
/// 默認主線程建立
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat block:(dispatch_block_t)block;
- (void)setTimeInterval:(NSTimeInterval)interval;
- (void)stop;
- (void)restart;
- (void)invalidate;
@end
複製代碼
而後咱們來實現方法:
@interface TGCDTimer() {
dispatch_source_t _timer;
BOOL _isFire;
}
@end
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat:(BOOL)repeat queue:(dispatch_queue_t)queue block:(dispatch_block_t)block {
// 1. 肯定傳進來的隊列是必須存在的
NSAssert(queue != NULL, @"queue can't be NULL while create TGCDTimer");
if (self = [super init]) {
// 2. 建立定時器
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 3. 設置定時器的時間
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
// 4. 監聽定時器的回調
dispatch_source_set_event_handler(_timer, ^{
// 將定時器的回調,傳給外界
if (block) {
block();
}
// 若是不重複,執行一次以後就銷燬定時器
if (!repeat) {
self->_isFire = NO;
dispatch_source_cancel(self->_timer);
}
});
// 5. 在給定的interval以後啓動定時器
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(self->_timer);
self->_isFire = YES;
});
}
return self;
}
複製代碼
實現定時器的重啓、暫停、銷燬等方法:
- (void)stop {
if (_isFire) {
_isFire = NO;
dispatch_suspend(_timer);
}
}
- (void)restart {
if (!_isFire) {
_isFire = YES;
dispatch_resume(_timer);
}
}
- (void)invalidate {
_isFire = NO;
dispatch_source_cancel(_timer);
}
- (void)dealloc {
_isFire = NO;
dispatch_source_cancel(_timer);
}
複製代碼
這樣一個簡單的GCD
定時器就實現了。
平時多使用定時器的時候,若是是要高頻的刷新UI
、渲染頁面就使用CADisplayLink
;此外,就直接使用dispatch_source
定時器。
具體代碼可參見TGCDTimer