前言
@interface NSTimer : NSObject
@interface CADisplayLink : NSObject
- 作用
- 在指定的時間執行指定的任務。
- 每隔一段時間執行指定的任務。
1、定時器的創建
1.1 NSTimer 定時器
-
當定時器創建完(不用 scheduled 的,添加到 runloop 中)后,該定時器將在初始化時指定的 ti 秒后自動觸發。如果 NSTimer 的觸發時間到的時候,runloop 在阻塞狀態,觸發時間就會推遲到下一個 runloop 周期。
-
1、scheduled 方式
-
創建並啟動定時器。
-
默認將時鍾以 NSDefaultRunLoopMode 模式添加到運行循環。
-
發生用戶交互的時候,時鍾會被暫停。
/* + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; 參數: TimeInterval:觸發時間,單位秒 target:定時起觸發對象 selector:定時器響應方法 userInfo:用戶信息 repeats:是否重復執行,YES 每個指定的時間重復執行,NO 只執行一次 */ // 創建並啟動定時器 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
-
-
2、timer 方式
-
創建定時器,添加到運行循環后啟動定時器。
-
將時鍾以指定的模式添加到運行循環。
/* + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; - (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode; NSDefaultRunLoopMode:發生用戶交互的時候,時鍾會被暫停。時鍾,網絡。 NSRunLoopCommonModes:發生用戶交互的時候,時鍾仍然會觸發,如果時鍾觸發方法非常耗時, 使用此方式時用戶操作會造成非常嚴重的卡頓。用戶交互,響應級別高。 */ // 創建定時器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES]; // 將定時器添加到運行循環 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-
1.2 CADisplayLink 定時器
-
1、CADisplayLink
-
CADisplayLink 是一個能讓我們以和屏幕刷新率相同的頻率將內容畫到屏幕上的定時器。我們在應用中創建一個新的 CADisplayLink 對象,把它添加到一個 runloop 中,並給它提供一個 target 和 selector 在屏幕刷新的時候調用。
-
一但 CADisplayLink 以特定的模式注冊到 runloop 之后,每當屏幕需要刷新的時候,runloop 就會調用 CADisplayLink 綁定的 target 上的 selector,這時 target 可以讀到 CADisplayLink 的每次調用的時間戳,用來准備下一幀顯示需要的數據。例如一個視頻應用使用時間戳來計算下一幀要顯示的視頻數據。在 UI 做動畫的過程中,需要通過時間戳來計算 UI 對象在動畫的下一幀要更新的大小等等。
-
在添加進 runloop 的時候我們應該選用高一些的優先級,來保證動畫的平滑。可以設想一下,我們在動畫的過程中,runloop 被添加進來了一個高優先級的任務,那么,下一次的調用就會被暫停轉而先去執行高優先級的任務,然后在接着執行 CADisplayLink 的調用,從而造成動畫過程的卡頓,使動畫不流暢。
-
另外 CADisplayLink 不能被繼承。
-
屬性:
duration :提供了每幀之間的時間,也就是屏幕每次刷新之間的的時間。我們可以使用這個時間來計算出下一幀要顯示 的 UI的數值。但是 duration 只是個大概的時間,如果 CPU 忙於其它計算,就沒法保證以相同的頻率 執行屏幕的繪制操作,這樣會跳過幾次調用回調方法的機會。 frameInterval :可讀可寫的 NSInteger 型值,標識間隔多少幀調用一次 selector 方法,默認值是 1,即每幀都調 用一次。如果每幀都調用一次的話,對於 iOS 設備來說那刷新頻率就是 60HZ 也就是每秒 60 次,如果 將 frameInterval 設為 2 那么就會兩幀調用一次,也就是變成了每秒刷新 30 次。 pause :控制 CADisplayLink 的運行。當我們想結束一個 CADisplayLink 的時候,應該調用 `- (void)invalidate`從 runloop 中刪除並刪除之前綁定的 target 跟 selector。
-
-
2、CADisplayLink 與 NSTimer 有什么不同
- iOS 設備的屏幕刷新頻率是固定的,CADisplayLink 在正常情況下會在每次刷新結束都被調用,精確度相當高。
- NSTimer 的精確度就顯得低了點,比如 NSTimer 的觸發時間到的時候,runloop 如果在阻塞狀態,觸發時間就會推遲到下一個 runloop 周期。並且 NSTimer 新增了 tolerance 屬性,讓用戶可以設置可以容忍的觸發的時間的延遲范圍。
- CADisplayLink 使用場合相對專一,適合做 UI 的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。NSTimer 的使用范圍要廣泛的多,各種需要單次或者循環定時處理的任務都可以使用。在 UI 相關的動畫或者顯示內容使用 CADisplayLink 比起用 NSTimer 的好處就是我們不需要再格外關心屏幕的刷新頻率了,因為它本身就是跟屏幕刷新同步的。
-
3、CADisplayLink 使用示例
@property (nonatomic, strong) CADisplayLink *link; // 創建定時器 self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(angleChange)]; [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; // 開啟定時器,默認 self.link.paused = NO; // 暫停定時器 self.link.paused = YES; // 定時器觸發事件處理 - (void)angleChange { }
@property (nonatomic, strong) CADisplayLink *displayLink; self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateTextColor)]; self.displayLink.paused = YES; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - (void)updateTextColor { } - (void)startAnimation { self.beginTime = CACurrentMediaTime(); self.displayLink.paused = NO; } - (void)stopAnimation { self.displayLink.paused = YES; [self.displayLink invalidate]; self.displayLink = nil; }
-
4、給非 UI 對象添加動畫效果
- 我們知道動畫效果就是一個屬性的線性變化,比如 UIView 動畫的 EasyIn EasyOut 。通過數值按照不同速率的變化我們能生成更接近真實世界的動畫效果。我們也可以利用這個特性來使一些其他屬性按照我們期望的曲線變化。比如當播放視頻時關掉視頻的聲音我可以通過 CADisplayLink 來實現一個 EasyOut 的漸出效果:先快速的降低音量,在慢慢的漸變到靜音。
-
5、注意
-
通常來講:iOS 設備的刷新頻率事 60HZ 也就是每秒 60 次。那么每一次刷新的時間就是 1/60 秒 大概 16.7 毫秒。當我們的 frameInterval 值為 1 的時候我們需要保證的是 CADisplayLink 調用的 target 的函數計算時間不應該大於 16.7 否則就會出現嚴重的丟幀現象。
-
在 Mac 應用中我們使用的不是 CADisplayLink 而是 CVDisplayLink 它是基於 C 接口的用起來配置有些麻煩但是用起來還是很簡單的。
-
2、定時器的啟動與關閉
// 啟動定時器
[timer setFireDate:[NSDate distantPast]];
// 暫停定時器
[timer setFireDate:[NSDate distantFuture]];
// 關閉定時器,永久關閉定時器
[timer invalidate];
3、子線程定時器的創建
-
在子線程創建定時器時,需要手動開啟子線程的運行循環。
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 在子線程創建定時器 /* scheduled 或 timer 方式創建 */ self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode]; // 啟動子線程的運行循環 /* 這句代碼就是一個死循環!如果不停止運行循環,不會執行添加到此句之后的任何代碼 */ CFRunLoopRun(); // 停止子線程運行循環之前,不會執行添加到此處的任何代碼 });
// 定時器執行操作方法 - (void)updateTimer { static int num = 0; num++; // 滿足條件后,停止當前的運行循環 if (num == 8) { // 停止當前的運行循環 /* 一旦停止了運行循環,后續代碼能夠執行,執行完畢后,線程被自動銷毀 */ CFRunLoopStop(CFRunLoopGetCurrent()); } }
4、定時任務
-
1)performSelector
// 延時調用 /* 1.5 秒后自動調用 self 的 hideHUD 方法 */ [self performSelector:@selector(hideHUD) withObject:nil afterDelay:1.5]; // 取消延時調用 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideHUD) object:nil];
-
2)GCD
// 多線程 /* 1.5 秒后自動執行 block 里面的代碼 */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.hud.alpha = 0.0; });
-
3)NSTimer
// 定時器 /* 1.5 秒后自動調用 self 的 hideHUD 方法 */ [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(hideHUD) userInfo:nil repeats:NO];