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

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

一. NSTimer

NSTimer的初始化方法有以下幾種:
會自動啟動, 并加入MainRunloopNSDefaultRunLoopMode 中:

注意: 這里的自動啟動, 并不是馬上就會啟動, 而是會延遲大概一個interval的時間:

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

參數(shù):

  • 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才開始執(zhí)行block里的內(nèi)容;

這里的停止定時器, 我直接在block里進行的, 如果使用一個全局變量來再其他地方手動停止定時器,需要這樣進行:

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

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

參數(shù):

  • ti: 重復執(zhí)行時間間隔
  • invocation: NSInvocation實例, 其用法見NSInvocation的基本用法
  • yesOrNo: 是否重復執(zhí)行

示例:

// 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:);
    
    // 設置參數(shù)
    // //這里的Index要從2開始,以為0跟1已經(jīng)被占據(jù)了,分別是self(target),selector(_cmd)
    // 如果有多個參數(shù), 可依次設置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>

可以看出, 這里定時器是立馬就執(zhí)行了, 沒有延遲;
此方法可以傳遞多個參數(shù), 下面是傳遞兩個參數(shù)的示例:

// 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:);
    
    // 設置參數(shù)
    // //這里的Index要從2開始,以為0跟1已經(jīng)被占據(jù)了,分別是self(target),selector(_cmd)
    // 如果有多個參數(shù), 可依次設置3 4 5 ...
    [invocation setArgument:&timer atIndex:2];
    // 設置第二個參數(shù)
    NSString *dsc = @"第二個參數(shù)是字符串";
    [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>--第二個參數(shù)是字符串
2016-12-29 16:57:45.088 定時器[12183:292324] start
2016-12-29 16:57:46.161 定時器[12183:292324] 1---<__NSCFTimer: 0x60000016dbc0>--第二個參數(shù)是字符串
2016-12-29 16:57:47.161 定時器[12183:292324] 2---<__NSCFTimer: 0x60000016dbc0>--第二個參數(shù)是字符串
2016-12-29 16:57:48.150 定時器[12183:292324] 3---<__NSCFTimer: 0x60000016dbc0>--第二個參數(shù)是字符串
2016-12-29 16:57:49.159 定時器[12183:292324] 4---<__NSCFTimer: 0x60000016dbc0>--第二個參數(shù)是字符串
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

參數(shù):

  • ti: 時間間隔
  • aTarget: 調用者
  • aSelector: 執(zhí)行的方法
  • userInfo: 參數(shù)
  • yesOrNo: 是否重復執(zhí)行

示例:

- (void)timer3 {
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(targetRun:) userInfo:@"這是攜帶的參數(shù)" 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>--這是攜帶的參數(shù)
2016-12-29 17:05:13.661 定時器[12328:296879] 1---<__NSCFTimer: 0x608000162700>--這是攜帶的參數(shù)
2016-12-29 17:05:14.664 定時器[12328:296879] 2---<__NSCFTimer: 0x608000162700>--這是攜帶的參數(shù)
2016-12-29 17:05:15.651 定時器[12328:296879] 3---<__NSCFTimer: 0x608000162700>--這是攜帶的參數(shù)
2016-12-29 17:05:16.650 定時器[12328:296879] 4---<__NSCFTimer: 0x608000162700>--這是攜帶的參數(shù)

下面這三種方式創(chuàng)建定時器的用法, 和上面相應的方法類似, 需要注意的是, 這樣創(chuàng)建的定時器, 并不會執(zhí)行, 需要我們手動來開啟定時器;

+ (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

定時器基本的創(chuàng)建方式就這些了, 還可以設置其他的屬性, 例如開啟時間, 這些直接參考其API 進行設置即可;

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

[timer invalidate];
 timer = nil;

二. GCD

dispatch_after : 延遲執(zhí)行一次
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后執(zhí)行");
    });
    
    NSLog(@"start");
}

重復執(zhí)行的定時器

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

參數(shù):

  • source: 定時器
  • start: 開始時間, 當我們使用 dispatch_time 或者 DISPATCH_TIME_NOW 時,系統(tǒng)會使用默認時鐘來進行計時。然而當系統(tǒng)休眠的時候,默認時鐘是不走的,也就會導致計時器停止。使用 dispatch_walltime 可以讓計時器按照真實時間間隔進行計時;
  • interval: 間隔(如果設置為 DISPATCH_TIME_FOREVER 則只執(zhí)行一次)
  • leeway: 允許的誤差范圍; 計時不可能是百分百精確的, 即使設置為0, 也不是百分百精確的, 所以可以設置合理的允許誤差, 單位: 納秒(NSEC_PER_SEC)

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

// 重復執(zhí)行的定時器
- (void)gcdTimer1 {
    
    // 獲取全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 創(chuàng)建定時器
    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);
    
    // 設置需要執(zhí)行的事件
    dispatch_source_set_event_handler(_timer, ^{
        
        //在這里執(zhí)行事件
        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之后才開始執(zhí)行,可以設置使用DISPATCH_TIME_NOW來立馬執(zhí)行;

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

dispatch_time_t start = dispatch_walltime(NULL, 0);

或者直接設置為: DISPATCH_TIME_NOW


關于dispatch_walltimedispatch_time 的區(qū)別, 上面也有提及,也可參考stackOverflow上的這個回答; 主要區(qū)別就是前者在系統(tǒng)休眠時還會繼續(xù)計時, 而后者在系統(tǒng)休眠時就停止計時, 待系統(tǒng)重新激活時, 接著繼續(xù)計時;

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

// 關閉定時器
// 完全銷毀定時器, 重新開啟的話需要重新創(chuàng)建
// 全局變量, 關閉后需要置為nil
dispatch_source_cancel(_timer);
            
// 暫停定時器
// 可使用dispatch_resume(_timer)再次開啟
// 全局變量, 暫停后不能置為nil, 否則不能重新開啟
dispatch_suspend(_timer);

三. CADisplayLink

CADisplayLink默認每秒運行60次,通過它的** frameInterval** 屬性改變每秒運行幀數(shù),如設置為2,意味CADisplayLink每隔一幀運行一次,有效的邏輯每秒運行30

屏幕刷新時調用:CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時器類。CADisplayLink以特定模式注冊到runloop后,每當屏幕顯示內(nèi)容刷新結束的時候,runloop就會向CADisplayLink指定的target發(fā)送一次指定的selector消息, CADisplayLink類對應的selector就會被調用一次。所以通常情況下,按照iOS設備屏幕的刷新率60次/秒

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

如果CPU過于繁忙,無法保證屏幕60次/秒的刷新率,就會導致跳過若干次調用回調方法的機會,跳過次數(shù)取決CPU的忙碌程度。

使用場景:從原理上可以看出,CADisplayLink適合做界面的不停重繪,比如視頻播放的時候需要不停地獲取下一幀用于界面渲染。

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

參數(shù):

  • target: 調用者
  • sel: 執(zhí)行的方法

示例:

- (void) displayLink {
    
    CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayRun:)];
    
    // 大概1s執(zhí)行一次
// 取值范圍 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 屬性, 來使其暫停, 或繼續(xù):

// 暫停
    display.paused = YES;
// 繼續(xù)
    display.paused = NO;
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 一. NSTimer NSTimer的初始化方法有以下幾種: 會自動啟動, 并加入* MainRunloop*的*...
    codeshow閱讀 608評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • 在軟件開發(fā)過程中,我們常常需要在某個時間后執(zhí)行某個方法,或者是按照某個周期一直執(zhí)行某個方法。在這個時候,我們就需要...
    誰遇而安閱讀 21,056評論 2 20
  • iOS中定時器有三種,分別是NSTimer、CADisplayLink、dispatch_source,下面就分別...
    HK_Hank閱讀 12,581評論 9 24
  • 秋季是多種疾病的好發(fā)季節(jié),常見病種和癥狀有以下九類: 1. 掉發(fā) 秋天氣候逐漸轉趨干燥,許...
    璞岸閱讀 365評論 0 0

友情鏈接更多精彩內(nèi)容