iOS-多線程(四)-GCD定時器

多線程(一)-原理
多線程(二)-GCD基礎
多線程(三)-GCD函數
多線程(四)-GCD定時器git

平常的開發中,咱們常常會用到計時器。在iOS中,有三種計時器,NSTimerCADisplayLinkdispatch_source,這三種定時器都是各有優劣。多線程

NSTimer

NSTimer是使用的比較多的一種,可是精度不夠,其緣由以下:函數

  1. NSTimer加在main runloop中,模式是NSDefaultRunLoopModemain負責全部主線程事務,例如UI界面的操做、複雜的運算等等,這樣在同一個runloopNSTimer就容易產生阻塞。
  2. 模式的改變也會影響到NSTimer的精度。主線程的runLoop裏有兩個預置的modekCFRunLoopDefaultModeUITrackingRunLoopMode。當你建立一個NSTimer並加到DefaultMode時,NSTimer會獲得重複回調,但此時若是滑動一個scrollView時,runLoop會將mode切換爲TrackingRunLoopMode,這時NSTimer就不會被回調。因此就會影響到NSTimer的精度。
  3. NSTimer會強引用target,而runLoop會強持有NSTimer,很容易出現內存泄漏。

那麼如何讓NSTimer精準一些?oop

  1. 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];
複製代碼
  1. 在子線程中進行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

    1. 建立源事件
    1. 設置定時器事件
    1. 設置事件觸發的回調
    1. 運行

另外咱們還須要在合適的地方設置源事件的數據。

其次,咱們知道,定義一個定時器,就須要運行、暫停、銷燬的功能。以及設置定時器定時的時間、是否重複、回調等配置。

另外,咱們要對外暴露實例方法、類方法來方便外界使用。

根據以上準備工做,咱們建立一個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

相關文章
相關標籤/搜索