# iOS CADisplayLink 簡介 CADisplayLink

CADisplayLink

CADisplayLink 是一個定時器

CADisplayLink是用於同步屏幕刷新頻率的計時器.

  • 創建CADisplayLink 對象時,要把它添加到一個runloop中,並給它提供一個target和select 在屏幕刷新的時候調用。

  • 一但 CADisplayLink 以特定的模式註冊到runloop之後,每當屏幕需要刷新的時候,runloop就會調用CADisplayLink綁定的target上的selector,這時target可以讀到 CADisplayLink 的每次調用的時間戳,用來準備下一幀顯示需要的數據。例如一個視頻應用使用時間戳來計算下一幀要顯示的視頻數據。在UI做動畫的過程中,需要通過時間戳來計算UI對象在動畫的下一幀要更新的大小等等。

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

Api


/*
需要注意 定時器對象創建後 並不會馬上執行 需要添加到runloop中
*/
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
//將當前定時器對象加入一個RunLoop中
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//將當前定時器對象從一個RunLoop中移除 如果這個Runloop是定時器所註冊的最後一個  移除後定時器將被釋放
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//將定時器失效掉 調用這個函數後 會將定時器從所有註冊的Runloop中移除
- (void)invalidate;
//當前時間戳
@property(readonly, nonatomic) CFTimeInterval timestamp;
//距離上次執行所間隔的時間
@property(readonly, nonatomic) CFTimeInterval duration;
//預計下次執行的時間戳
@property(readonly, nonatomic) CFTimeInterval targetTimestamp;
//設置是否暫停
@property(getter=isPaused, nonatomic) BOOL paused;
//設置預期的每秒執行幀數 例如設置爲1 則以每秒一次的速率執行
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
//同上 
@property(nonatomic) NSInteger frameInterval
  CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");
  

timestamp 時間戳

這個屬性用來返回上一次屏幕刷新的時間戳.如果視頻播放的應用,可以通過時間戳來獲取上一幀的具體時間,來計算下一幀.

duration屬性

  • duration屬性提供了每幀之間的時間,也就是屏幕每次刷新之間的的時間。我們可以使用這個時間來計算出下一幀要顯示的UI的數值。但是 duration只是個大概的時間,如果CPU忙於其它計算,就沒法保證以相同的頻率執行屏幕的繪製操作,這樣會跳過幾次調用回調方法的機會。

frameInterval屬性

  • frameInterval屬性是可讀可寫的NSInteger型值,標識間隔多少幀調用一次selector 方法,默認值是1,即每幀都調用一次。如果每幀都調用一次的話,對於iOS設備來說那刷新頻率就是60HZ也就是每秒60次,如果將 frameInterval 設爲2 那麼就會兩幀調用一次,也就是變成了每秒刷新30次。
  • 我們通過pause屬性開控制CADisplayLink的運行。當我們想結束一個CADisplayLink的時候,應該調用-(void)invalidate
  • 從runloop中刪除並刪除之前綁定的 target跟selector
  • 另外CADisplayLink 不能被繼承。

修改幀率

如果在特定幀率內無法提供對象的操作,可以通過降低幀率解決.一個擁有持續穩定但是較慢幀率的應用要比跳幀的應用順滑的多.
可以通過preferredFramesPerSecond來設置每秒刷新次數.preferredFramesPerSecond默認值爲屏幕最大幀率(maximumFramesPerSecond),目前是60.
實際的屏幕幀率會和preferredFramesPerSecond有一定的出入,結果是由設置的值和屏幕最大幀率(maximumFramesPerSecond)相互影響產生的.規則大概如下:

如果屏幕最大幀率(preferredFramesPerSecond)是60,實際幀率只能是15, 20, 30, 60中的一種.如果設置大於60的值,屏幕實際幀率爲60.如果設置的是26~35之間的值,實際幀率是30.如果設置爲0,會使用最高幀率.

CADisplayLink 與 NSTimer 有什麼不同

  • iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結束都被調用,精確度相當高。

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

  • CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。NSTimer的使用範圍要廣泛的多,各種需要單次或者循環定時處理的任務都可以使用。在UI相關的動畫或者顯示內容使用 CADisplayLink比起用NSTimer的好處就是我們不需要在格外關心屏幕的刷新頻率了,因爲它本身就是跟屏幕刷新同步的。

PS:通常來講:iOS設備的刷新頻率事60HZ也就是每秒60次。那麼每一次刷新的時間就是1/60秒 大概16.7毫秒。當我們的frameInterval值爲1的時候我們需要保證的是 CADisplayLink調用的`target`的函數計算時間不應該大於 16.7否則就會出現嚴重的丟幀現象。

測試代碼:
用了一個圖片,來旋轉圖片,看到的效果是 圖片一直在旋轉


- (void)viewDidLoad {
    [super viewDidLoad];
    [self _initUI];
}


- (void)_initUI
{
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkClick)];
    self.link = link;
    
    link.paused = NO;
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)linkClick
{
    NSLog(@"_______linkClick");
    
    _testImg.transform = CGAffineTransformRotate(_testImg.transform, M_PI / 240);
}

- (void)startAnimation
{
    CFTimeInterval beginTime = CACurrentMediaTime();

    self.link.paused = NO;
}

- (void)stopAnimation {
    
    self.link.paused = YES;
    [self.link invalidate];
    self.link = nil;

}

參考鏈接點我
Demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章