前言:相信一說到定時器, 我們使用最多的就是NSTimer 和 GCD 了, 還有另外一個高級的定時器 CADisplayLink;,下面將給大家詳細介紹關于iOS定時器使用的相關內容,話不多說了,來一起看看詳細的介紹吧。
一. NSTimer
NSTimer的初始化方法有以下幾種:
會自動啟動, 并加入 MainRunloop 的 NSDefaultRunLoopMode 中,注意: 這里的自動啟動, 并不是馬上就會啟動, 而是會延遲大概一個interval的時間:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
參數:
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里的內容;
這里的停止定時器, 我直接在block里進行的, 如果使用一個全局變量來再其他地方手動停止定時器,需要這樣進行:
- [self.timer invalidate];
- self.timer = nil;
- + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo
參數:
ti: 重復執行時間間隔
invocation: NSInvocation實例, 其用法見NSInvocation的基本用法
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;
新聞熱點
疑難解答