[iOS]各類定時器--最全的定時器使用

說到定時器, 咱們使用最多的就是NSTimerGCD 了, 還有另一個高級的定時器 CADisplayLink;oop

一. NSTimer

NSTimer的初始化方法有如下幾種:
會自動啓動, 並加入 MainRunloop NSDefaultRunLoopMode 中,ui

注意: 這裏的自動啓動, 並非立刻就會啓動, 而是會延遲大概一個interval的時間:spa

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block

參數:code

  • internal : 時間間隔, 多久調用一次視頻

  • repeats: 是否重複調用對象

  • block: 須要重複作的事情隊列

使用:事件

[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            [timer invalidate];
            
            NSLog(@"end");
        }
    }];
    
    NSLog(@"start");

這時, 控制檯的輸出:字符串

2016-12-29 16:29:53.901 定時器[11673:278678] start
2016-12-29 16:29:54.919 定時器[11673:278678] 0
2016-12-29 16:29:55.965 定時器[11673:278678] 1
2016-12-29 16:29:56.901 定時器[11673:278678] 2
2016-12-29 16:29:57.974 定時器[11673:278678] 3
2016-12-29 16:29:58.958 定時器[11673:278678] 4
2016-12-29 16:29:58.959 定時器[11673:278678] end

能夠看出, 這裏的internal設置爲1s, 大概延遲了1s纔開始執行block裏的內容;get

這裏的中止定時器, 我直接在block裏進行的, 若是使用一個全局變量來再其餘地方手動中止定時器,須要這樣進行:

[self.timer invalidate];
self.timer = nil;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo

參數:

示例:

// NSInvocation形式
- (void)timer2 {
    
    NSMethodSignature *method = [ViewController instanceMethodSignatureForSelector:@selector(invocationTimeRun:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:method];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES];
    
    // 設置方法調用者
    invocation.target = self;
    
    // 這裏的SEL須要和NSMethodSignature中的一致
    invocation.selector = @selector(invocationTimeRun:);
    
    // 設置參數
    // //這裏的Index要從2開始,覺得0跟1已經被佔據了,分別是self(target),selector(_cmd)
    // 若是有多個參數, 可依次設置3 4 5 ...
    [invocation setArgument:&timer atIndex:2];
    
    [invocation invoke];
    
    NSLog(@"start");
}
- (void)invocationTimeRun:(NSTimer *)timer {
    
    static NSInteger num = 0;
    NSLog(@"%ld---%@", (long)num, timer);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

輸出:

2016-12-29 16:52:54.029 定時器[12089:289673] 0---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:54.029 定時器[12089:289673] start
2016-12-29 16:52:55.104 定時器[12089:289673] 1---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:56.095 定時器[12089:289673] 2---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:57.098 定時器[12089:289673] 3---<__NSCFTimer: 0x60000017d940>
2016-12-29 16:52:58.094 定時器[12089:289673] 4---<__NSCFTimer: 0x60000017d940>

能夠看出, 這裏定時器是立馬就執行了, 沒有延遲;
此方法能夠傳遞多個參數, 下面是傳遞兩個參數的示例:

// NSInvocation形式
- (void)timer2 {
    
    NSMethodSignature *method = [ViewController instanceMethodSignatureForSelector:@selector(invocationTimeRun:des:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:method];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 invocation:invocation repeats:YES];
    
    // 設置方法調用者
    invocation.target = self;
    
    // 這裏的SEL須要和NSMethodSignature中的一致
    invocation.selector = @selector(invocationTimeRun:des:);
    
    // 設置參數
    // //這裏的Index要從2開始,覺得0跟1已經被佔據了,分別是self(target),selector(_cmd)
    // 若是有多個參數, 可依次設置3 4 5 ...
    [invocation setArgument:&timer atIndex:2];
    // 設置第二個參數
    NSString *dsc = @"第二個參數是字符串";
    [invocation setArgument:&dsc atIndex:3];
    
    [invocation invoke];
    
    NSLog(@"start");
}
- (void)invocationTimeRun:(NSTimer *)timer des:(NSString *)dsc {
    
    static NSInteger num = 0;
    NSLog(@"%ld---%@--%@", (long)num, timer, dsc);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

輸出:

2016-12-29 16:57:45.087 定時器[12183:292324] 0---<__NSCFTimer: 0x60000016dbc0>--第二個參數是字符串
2016-12-29 16:57:45.088 定時器[12183:292324] start
2016-12-29 16:57:46.161 定時器[12183:292324] 1---<__NSCFTimer: 0x60000016dbc0>--第二個參數是字符串
2016-12-29 16:57:47.161 定時器[12183:292324] 2---<__NSCFTimer: 0x60000016dbc0>--第二個參數是字符串
2016-12-29 16:57:48.150 定時器[12183:292324] 3---<__NSCFTimer: 0x60000016dbc0>--第二個參數是字符串
2016-12-29 16:57:49.159 定時器[12183:292324] 4---<__NSCFTimer: 0x60000016dbc0>--第二個參數是字符串
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

參數:

  • ti: 時間間隔

  • aTarget: 調用者

  • aSelector: 執行的方法

  • userInfo: 參數

  • yesOrNo: 是否重複執行

示例:

- (void)timer3 {
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(targetRun:) userInfo:@"這是攜帶的參數" repeats:YES];
    
    NSLog(@"start");
}
- (void)targetRun:(NSTimer *)timer {
    
    static NSInteger num = 0;
    
    NSLog(@"%ld---%@--%@", (long)num, timer, timer.userInfo);
    
    num++;
    
    if (num > 4) {
        [timer invalidate];
    }
}

輸出:

2016-12-29 17:05:11.590 定時器[12328:296879] start
2016-12-29 17:05:12.655 定時器[12328:296879] 0---<__NSCFTimer: 0x608000162700>--這是攜帶的參數
2016-12-29 17:05:13.661 定時器[12328:296879] 1---<__NSCFTimer: 0x608000162700>--這是攜帶的參數
2016-12-29 17:05:14.664 定時器[12328:296879] 2---<__NSCFTimer: 0x608000162700>--這是攜帶的參數
2016-12-29 17:05:15.651 定時器[12328:296879] 3---<__NSCFTimer: 0x608000162700>--這是攜帶的參數
2016-12-29 17:05:16.650 定時器[12328:296879] 4---<__NSCFTimer: 0x608000162700>--這是攜帶的參數

下面這三種方式建立定時器的用法, 和上面相應的方法相似, 須要注意的是, 這樣建立的定時器, 並不會執行, 須要咱們手動來開啓定時器;

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo

開啓的方式是, 將當前定時器添加到RunLoop中:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

下面給出一個示例:

- (void)timer4 {
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            [timer invalidate];
            timer = nil;
            
            NSLog(@"end");
        }
    }];
    
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    NSLog(@"start");
}

輸出:

2016-12-29 17:12:13.955 定時器[12498:301751] start
2016-12-29 17:12:15.013 定時器[12498:301751] 0
2016-12-29 17:12:16.018 定時器[12498:301751] 1
2016-12-29 17:12:17.011 定時器[12498:301751] 2
2016-12-29 17:12:18.024 定時器[12498:301751] 3
2016-12-29 17:12:19.023 定時器[12498:301751] 4
2016-12-29 17:12:19.023 定時器[12498:301751] end

定時器基本的建立方式就這些了, 還能夠設置其餘的屬性, 例如開啓時間, 這些直接參考其API 進行設置便可;

注意: 以上實例中, 我沒有使用全局的NSTimer 對象, 若是設置全局變量, 或者設置爲屬性, 在中止定時器的時候要手動置爲nil, 即:

[timer invalidate];
 timer = nil;

二. GCD

dispatch_after : 延遲執行一次
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)

示例:

- (void)gcdTimer {
    
    // 延遲2s
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    
    dispatch_after(delayTime, dispatch_get_main_queue(), ^(void){
        
        NSLog(@"延遲2s後執行");
    });
    
    NSLog(@"start");
}

重複執行的定時器

void
dispatch_source_set_timer(dispatch_source_t source,
    dispatch_time_t start,
    uint64_t interval,
    uint64_t leeway)

參數:

  • source: 定時器

  • start: 開始時間, 當咱們使用 dispatch_time 或者 DISPATCH_TIME_NOW 時,系統會使用默認時鐘來進行計時。然而當系統休眠的時候,默認時鐘是不走的,也就會致使計時器中止。使用 dispatch_walltime 可讓計時器按照真實時間間隔進行計時;

  • interval: 間隔(若是設置爲 DISPATCH_TIME_FOREVER 則只執行一次)

  • leeway: 容許的偏差範圍; 計時不多是百分百精確的, 即便設置爲0, 也不是百分百精確的, 因此能夠設置合理的容許偏差, 單位: 納秒(NSEC_PER_SEC)


相關內容, 可參考文章: Dispatch Source Timer 的使用以及注意事項

// 重複執行的定時器
- (void)gcdTimer1 {
    
    // 獲取全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 建立定時器
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 開始時間
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    
//    dispatch_time_t start = dispatch_walltime(NULL, 0);
    
    // 重複間隔
    uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    
    // 設置定時器
    dispatch_source_set_timer(_timer, start, interval, 0);
    
    // 設置須要執行的事件
    dispatch_source_set_event_handler(_timer, ^{
        
        //在這裏執行事件
        static NSInteger num = 0;
        
        NSLog(@"%ld", (long)num);
        num++;
        
        if (num > 4) {
            
            NSLog(@"end");
            
            // 關閉定時器
            dispatch_source_cancel(_timer);
        }
    });
    // 開啓定時器
    dispatch_resume(_timer);
    
    NSLog(@"start");
}

輸出:

2016-12-30 10:15:01.114 定時器[3393:99474] start
2016-12-30 10:15:02.187 定時器[3393:99796] 0
2016-12-30 10:15:03.114 定時器[3393:99796] 1
2016-12-30 10:15:04.186 定時器[3393:99796] 2
2016-12-30 10:15:05.188 定時器[3393:99796] 3
2016-12-30 10:15:06.188 定時器[3393:99796] 4
2016-12-30 10:15:06.188 定時器[3393:99796] end

這裏的開始時間設置了1s的間隔, 因此1s以後纔開始執行,能夠設置使用DISPATCH_TIME_NOW來立馬執行;

注意:
這裏的開始時間(start)可使用下面的方式的來設置:

dispatch_time_t start = dispatch_walltime(NULL, 0);

或者直接設置爲: DISPATCH_TIME_NOW


關於 dispatch_walltime dispatch_time 的區別, 上面也有說起,也可參考stackOverflow上的這個回答; 主要區別就是前者在系統休眠時還會繼續計時, 然後者在系統休眠時就中止計時, 待系統從新激活時, 接着繼續計時;

中止計時器:
中止GCD定時器的方式, Dispatch Source Timer 的使用以及注意事項中有說起, 主要有如下兩種:

// 關閉定時器
// 徹底銷燬定時器, 從新開啓的話須要從新建立
// 全局變量, 關閉後須要置爲nil
dispatch_source_cancel(_timer);
            
// 暫停定時器
// 可以使用dispatch_resume(_timer)再次開啓
// 全局變量, 暫停後不能置爲nil, 不然不能從新開啓
dispatch_suspend(_timer);

三. CADisplayLink

CADisplayLink默認每秒運行60次,經過它的 frameInterval 屬性改變每秒運行幀數,如設置爲2,意味CADisplayLink每隔一幀運行一次,有效的邏輯每秒運行30

屏幕刷新時調用:CADisplayLink是一個能讓咱們以和屏幕刷新率同步的頻率將特定的內容畫到屏幕上的定時器類。CADisplayLink以特定模式註冊到runloop後,每當屏幕顯示內容刷新結束的時候,runloop就會向CADisplayLink指定的target發送一次指定的selector消息, CADisplayLink類對應的selector就會被調用一次。因此一般狀況下,按照iOS設備屏幕的刷新率60次/秒

延遲:iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常狀況下會在每次刷新結束都被調用,精確度至關高。但若是調用的方法比較耗時,超過了屏幕刷新週期,就會致使跳過若干次回調調用機會。

若是CPU過於繁忙,沒法保證屏幕60次/秒的刷新率,就會致使跳過若干次調用回調方法的機會,跳過次數取決CPU的忙碌程度。

使用場景:從原理上能夠看出,CADisplayLink適合作界面的不停重繪,好比視頻播放的時候須要不停地獲取下一幀用於界面渲染。

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel

參數:

  • target: 調用者

  • sel: 執行的方法

示例:

- (void) displayLink {
    
    CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayRun:)];
    
    // 大概1s執行一次
// 取值範圍 1--100, 值越大, 頻率越高
    display.preferredFramesPerSecond = 2;
    
    [display addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)displayRun:(CADisplayLink *)link {
    
    static NSInteger num = 0;
    
    NSLog(@"%ld", (long)num);
    num++;
    
    if (num > 4) {
        
        [link invalidate];
        
        NSLog(@"end");
    }
}

這裏的示例不太恰當, 不該該在這種場合使用,
另外, 咱們可使用他的 paused 屬性, 來使其暫停, 或繼續:

// 暫停
    display.paused = YES;
// 繼續
    display.paused = NO;
相關文章
相關標籤/搜索