iOS開發中深入理解CADisplayLink和NSTimer

海之飛燕發表於2016-08-28

一、什麼是CADisplayLink

簡單地說,它就是一個定時器,每隔幾毫秒重新整理一次螢幕。

CADisplayLink是一個能讓我們以和螢幕重新整理率相同的頻率將內容畫到螢幕上的定時器。我們在應用中建立一個新的 CADisplayLink 物件,把它新增到一個runloop中,並給它提供一個 target 和 selector 在螢幕重新整理的時候呼叫。

一但 CADisplayLink 以特定的模式註冊到runloop之後,每當螢幕需要重新整理的時候,runloop就會呼叫CADisplayLink繫結的target上的selector,這時target可以讀到 CADisplayLink 的每次呼叫的時間戳,用來準備下一幀顯示需要的資料。例如一個視訊應用使用時間戳來計算下一幀要顯示的視訊資料。在UI做動畫的過程中,需要通過時間戳來計算UI物件在動畫的下一幀要更新的大小等等。

在新增進runloop的時候我們應該選用高一些的優先順序,來保證動畫的平滑。可以設想一下,我們在動畫的過程中,runloop被新增進來了一個高優先順序的任務,那麼,下一次的呼叫就會被暫停轉而先去執行高優先順序的任務,然後在接著執行CADisplayLink的呼叫,從而造成動畫過程的卡頓,使動畫不流暢。

duration屬性:提供了每幀之間的時間,也就是螢幕每次重新整理之間的的時間。該屬性在target的selector被首次呼叫以後才會被賦值。selector的呼叫間隔時間計算方式是:時間=duration×frameInterval。 我們可以使用這個時間來計算出下一幀要顯示的UI的數值。但是 duration只是個大概的時間,如果CPU忙於其它計算,就沒法保證以相同的頻率執行螢幕的繪製操作,這樣會跳過幾次呼叫回撥方法的機會。

frameInterval屬性:是可讀可寫的NSInteger型值,標識間隔多少幀呼叫一次selector 方法,預設值是1,即每幀都呼叫一次。如果每幀都呼叫一次的話,對於iOS裝置來說那重新整理頻率就是60HZ也就是每秒60次,如果將 frameInterval 設為2 那麼就會兩幀呼叫一次,也就是變成了每秒重新整理30次。

pause屬性:控制CADisplayLink的執行。當我們想結束一個CADisplayLink的時候,應該呼叫-(void)invalidate 從runloop中刪除並刪除之前繫結的 target 跟 selector

timestamp屬性: 只讀的CFTimeInterval值,表示螢幕顯示的上一幀的時間戳,這個屬性通常被target用來計算下一幀中應該顯示的內容。 列印timestamp值,其樣式類似於:179699.631584。

另外 CADisplayLink 不能被繼承。

給非UI物件新增動畫效果

我們知道動畫效果就是一個屬性的線性變化,比如 UIView 動畫的 EasyIn EasyOut 。通過數值按照不同速率的變化我們能生成更接近真實世界的動畫效果。我們也可以利用這個特性來使一些其他屬性按照我們期望的曲線變化。比如當播放視訊時關掉視訊的聲音我可以通過 CADisplayLink 來實現一個 EasyOut 的漸出效果:先快速的降低音量,在慢慢的漸變到靜音。

注意

通常來講:iOS裝置的重新整理頻率事60HZ也就是每秒60次。那麼每一次重新整理的時間就是1/60秒 大概16.7毫秒。當我們的frameInterval值為1的時候我們需要保證的是 CADisplayLink呼叫的target的函式計算時間不應該大於 16.7否則就會出現嚴重的丟幀現象。 在mac應用中我們使用的不是CADisplayLink而是 CVDisplayLink它是基於C介面的用起來配置有些麻煩但是用起來還是很簡單的。

二、CADisplayLink 與 NSTimer 有什麼不同?

1.原理不同

CADisplayLink是一個能讓我們以和螢幕重新整理率同步的頻率將特定的內容畫到螢幕上的定時器類。 CADisplayLink以特定模式註冊到runloop後, 每當螢幕顯示內容重新整理結束的時候,runloop就會向 CADisplayLink指定的target傳送一次指定的selector訊息, CADisplayLink類對應的selector就會被呼叫一次。

NSTimer以指定的模式註冊到runloop後,每當設定的週期時間到達後,runloop會向指定的target傳送一次指定的selector訊息。

2.週期設定方式不同

iOS裝置的螢幕重新整理頻率(FPS)是60Hz,因此CADisplayLink的selector 預設呼叫週期是每秒60次,這個週期可以通過frameInterval屬性設定, CADisplayLink的selector每秒呼叫次數=60/ frameInterval。比如當 frameInterval設為2,每秒呼叫就變成30次。因此, CADisplayLink 週期的設定方式略顯不便。

NSTimer的selector呼叫週期可以在初始化時直接設定,相對就靈活的多。

3、精確度不同

iOS裝置的螢幕重新整理頻率是固定的,CADisplayLink在正常情況下會在每次重新整理結束都被呼叫,精確度相當高。

NSTimer的精確度就顯得低了點,比如NSTimer的觸發時間到的時候,runloop如果在阻塞狀態,觸發時間就會推遲到下一個runloop週期。並且 NSTimer新增了tolerance屬性,讓使用者可以設定可以容忍的觸發的時間的延遲範圍。

4、使用場景

CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視訊播放的渲染。

NSTimer的使用範圍要廣泛的多,各種需要單次或者迴圈定時處理的任務都可以使用。

三、CADisplayLink和NSTimer的使用

CADisplayLink的使用

1.建立方法

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];

[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

2.停止方法

[self.displayLink invalidate];

self.displayLink = nil;

當把CADisplayLink物件add到runloop中後,selector就能被週期性呼叫,類似於重複的NSTimer被啟動了;執行invalidate操作時,CADisplayLink物件就會從runloop中移除,selector呼叫也隨即停止,類似於NSTimer的invalidate方法。

NSTimer的使用

1. 建立方法

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:NO];

TimerInterval : 執行之前等待的時間。比如設定成1.0,就代表1秒後執行方法

target : 需要執行方法的物件。

selector : 需要執行的方法

repeats : 是否需要迴圈

2. 釋放方法

[timer invalidate];

注意 :呼叫建立方法後,target物件的計數器會加1,直到執行完畢,自動減1。如果是迴圈執行的話,就必須手動關閉,否則可以不執行釋放方法。

3. 特性

存在延遲 ,不管是一次性的還是週期性的timer的實際觸發事件的時間,都會與所加入的RunLoop和RunLoop Mode有關,如果此RunLoop正在執行一個連續性的運算,timer就會被延時出發。重複性的timer遇到這種情況,如果延遲超過了一個週期,則會在延時結束後立刻執行,並按照之前指定的週期繼續執行。

注意:必須加入Runloop

使用上面的建立方式,會自動把timer加入MainRunloop的NSDefaultRunLoopMode中。如果使用以下方式建立定時器,就必須手動加入Runloop:

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

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

相關文章