深入理解CADisplayLink和NSTimer

?? 今天寫Demo的時候用到了NSTimer赡磅,然后腦子一熱就把CADisplayLink的用法也寫了一遍魄缚,就當(dāng)是過一遍大腦涼快涼快。哈哈,快玩笑冶匹,直接上重點习劫。

一、什么是CADisplayLink

?? CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫到屏幕上的定時器嚼隘。我們在應(yīng)用中創(chuàng)建一個新的CADisplayLink 對象诽里,把它添加到一個RunLoop中,并給它提供一個 target 和 selector 在屏幕刷新的時候調(diào)用飞蛹。
?? 一但 CADisplayLink 以特定的模式注冊到RunLoop之后谤狡,每當(dāng)屏幕需要刷新的時候,RunLoop就會調(diào)用CADisplayLink綁定的target上的selector卧檐,這時target可以讀到 CADisplayLink 的每次調(diào)用的時間戳墓懂,用來準(zhǔn)備下一幀顯示需要的數(shù)據(jù)。
?? 在添加進(jìn)RunLoop的時候我們應(yīng)該選用高一些的優(yōu)先級霉囚,來保證動畫的平滑捕仔。可以設(shè)想一下盈罐,我們在動畫的過程中榜跌,RunLoop被添加進(jìn)來了一個高優(yōu)先級的任務(wù),那么盅粪,下一次的調(diào)用就會被暫停轉(zhuǎn)而先去執(zhí)行高優(yōu)先級的任務(wù)钓葫,然后在接著執(zhí)行CADisplayLink的調(diào)用,從而造成動畫過程的卡頓票顾,使動畫不流暢础浮。
?? 簡單地說傅寡,它就是一個定時器,每隔幾毫秒刷新一次屏幕梧却。

CADisplayLink的常用屬性介紹

?? frameInterval屬性:是可讀可寫的NSInteger型值伏恐,標(biāo)識間隔多少幀調(diào)用一次selector 方法,默認(rèn)值是1扯饶,即每幀都調(diào)用一次。如果每幀都調(diào)用一次的話,對于iOS設(shè)備來說那刷新頻率就是60HZ也就是每秒60次民晒,如果將 frameInterval 設(shè)為2 那么就會兩幀調(diào)用一次,也就是變成了每秒刷新30次锄禽。
?? duration屬性:提供了每幀之間的時間潜必,也就是屏幕每次刷新之間的的時間。該屬性在target的selector被首次調(diào)用以后才會被賦值沃但。selector的調(diào)用間隔時間計算方式是:時間 = duration×frameInterval磁滚。 我們可以使用這個時間來計算出下一幀要顯示的UI的數(shù)值。但是 duration只是個大概的時間,如果CPU忙于其它計算垂攘,就沒法保證以相同的頻率執(zhí)行屏幕的繪制操作维雇,這樣會跳過幾次調(diào)用回調(diào)方法的機(jī)會。
?? pause屬性:控制 CADisplayLink的運行晒他。當(dāng)我們想結(jié)束一個CADisplayLink的時候吱型,應(yīng)該調(diào)用-(void)invalidateRunLoop中刪除并刪除之前綁定的 target 跟 selector
?? timestamp屬性: 只讀的CFTimeInterval值,表示屏幕顯示的上一幀的時間戳陨仅,這個屬性通常被target用來計算下一幀中應(yīng)該顯示的內(nèi)容津滞。

二、CADisplayLink 與NSTimer有什么不同?

1灼伤、原理不同

??CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時器類触徐。 CADisplayLink以特定模式注冊到runloop后, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候饺蔑,runloop就會向 CADisplayLink指定的target發(fā)送一次指定的selector消息锌介, CADisplayLink類對應(yīng)的selector就會被調(diào)用一次。
??NSTimer以指定的模式注冊到runloop后猾警,每當(dāng)設(shè)定的周期時間到達(dá)后孔祸,runloop會向指定的target發(fā)送一次指定的selector消息。

2发皿、周期設(shè)置方式不同

??iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz崔慧,因此CADisplayLink的selector 默認(rèn)調(diào)用周期是每秒60次,這個周期可以通過frameInterval屬性設(shè)置穴墅,CADisplayLink的selector每秒調(diào)用次數(shù) = 60/ frameInterval惶室。比如當(dāng) frameInterval設(shè)為2,每秒調(diào)用就變成30次玄货。因此皇钞,CADisplayLink 周期的設(shè)置方式略顯不便。
??NSTimer的selector調(diào)用周期可以在初始化時直接設(shè)定松捉,相對就靈活的多夹界。

3、精確度不同

??iOS設(shè)備的屏幕刷新頻率是固定的隘世,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用可柿,精確度相當(dāng)高。
??NSTimer的精確度就顯得低了點丙者,比如NSTimer的觸發(fā)時間到的時候复斥,runloop如果在阻塞狀態(tài),觸發(fā)時間就會推遲到下一個runloop周期械媒。并且 NSTimer新增了tolerance屬性目锭,讓用戶可以設(shè)置可以容忍的觸發(fā)的時間的延遲范圍。

4、使用場景

??CADisplayLink使用場合相對專一痢虹,適合做UI的不停重繪键俱,比如自定義動畫引擎或者視頻播放的渲染。
??NSTimer的使用范圍要廣泛的多世分,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用编振。

三、CADisplayLink和NSTimer的使用

(一)臭埋、 CADisplayLink的使用

1.創(chuàng)建方法

displayLink = CADisplayLink(target: self, selector: #selector(showImage))
// ios10之前是`frameInterval`.ios10之后是`preferredFramesPerSecond `
displayLink?.preferredFramesPerSecond = 1
        // 添加到RunLoop
displayLink?.add(to: RunLoop.current, forMode: .defaultRunLoopMode)

@objc func showImage() {
}

2.停止方法

// 停止
displayLink?.invalidate()
//從runloop中移除
displayLink?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode)

當(dāng)把CADisplayLink對象add到runloop中后踪央,selector就能被周期性調(diào)用,類似于重復(fù)的NSTimer被啟動了瓢阴;執(zhí)行invalidate()操作時畅蹂,CADisplayLink對象就會從runloop中移除,selector調(diào)用也隨即停止荣恐,類似于NSTimerinvalidate()方法液斜。

(二)、 NSTimer的使用
  1. 創(chuàng)建方法
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(showImage), userInfo: nil, repeats: true)
@objc func showImage() {

}

TimerInterval : 執(zhí)行之前等待的時間叠穆。比如設(shè)置成1.0少漆,就代表1秒后執(zhí)行方法
target : 需要執(zhí)行方法的對象。
selector : 需要執(zhí)行的方法
repeats : 是否需要循環(huán)

  1. 釋放方法
 timer?.invalidate()
 timer = nil

注意:調(diào)用創(chuàng)建方法后硼被,target對象的計數(shù)器會加1示损,直到執(zhí)行完畢,自動減1嚷硫。如果是循環(huán)執(zhí)行的話检访,就必須手動關(guān)閉,否則可以不執(zhí)行釋放方法仔掸。

  1. 特性

??存在延遲 脆贵,不管是一次性的還是周期性的timer的實際觸發(fā)事件的時間,都會與所加入的RunLoopRunLoop Mode有關(guān)起暮,如果此RunLoop正在執(zhí)行一個連續(xù)性的運算卖氨,timer就會被延時出發(fā)。重復(fù)性的timer遇到這種情況鞋怀,如果延遲超過了一個周期双泪,則會在延時結(jié)束后立刻執(zhí)行持搜,并按照之前指定的周期繼續(xù)執(zhí)行密似。
??使用上面的創(chuàng)建方式,會自動把timer加入MainRunloopNSDefaultRunLoopMode中葫盼。如果使用以下方式創(chuàng)建定時器残腌,就必須手動加入Runloop:

timer = Timer(timeInterval: 0.1, target: self, selector: #selector(showImage), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .defaultRunLoopMode)
        

注意:必須加入Runloop, 否則不會調(diào)用showImage()函數(shù)

全部代碼如下:

//
//  VC6.swift
//  iOS動畫之顯示層初級動畫效果
//
//  Created by soliloquy on 2018/3/8.
//  Copyright ? 2018年 soliloquy. All rights reserved.
//

import UIKit

class VC6: UIViewController {


    var imageView:UIImageView?
    var timer:Timer?
    var index:Int = 0
    var displayLink:CADisplayLink?

    override func viewDidLoad() {
        super.viewDidLoad()

        
        view.backgroundColor = UIColor.white
        
        imageView = UIImageView()
        imageView?.frame = view.bounds
        imageView?.contentMode = .scaleAspectFit
        view.addSubview(imageView!)
        
        // Timer
//        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(showImage), userInfo: nil, repeats: true)
        
        timer = Timer(timeInterval: 0.1, target: self, selector: #selector(showImage), userInfo: nil, repeats: true)
        RunLoop.current.add(timer!, forMode: .defaultRunLoopMode)
        
////        CADisplayLink
//        displayLink = CADisplayLink(target: self, selector: #selector(showImage))
        displayLink?.preferredFramesPerSecond = 1
//        // 添加到RunLoop
//        displayLink?.add(to: RunLoop.current, forMode: .defaultRunLoopMode)
        
    }

    @objc func showImage() {
        imageView?.image = UIImage(named: "\(index)")
        index += 1
        if index == 67 {
//            timer?.invalidate()
//            timer = nil
            displayLink?.invalidate()
            displayLink?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode)
            index -= 1
            imageView?.image = UIImage(named: "\(index)")
        }
    }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抛猫,更是在濱河造成了極大的恐慌蟆盹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闺金,死亡現(xiàn)場離奇詭異逾滥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)败匹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門寨昙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掀亩,你說我怎么就攤上這事舔哪。” “怎么了槽棍?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵捉蚤,是天一觀的道長。 經(jīng)常有香客問我炼七,道長缆巧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任豌拙,我火速辦了婚禮盅蝗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姆蘸。我一直安慰自己墩莫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布逞敷。 她就那樣靜靜地躺著狂秦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪推捐。 梳的紋絲不亂的頭發(fā)上裂问,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機(jī)與錄音牛柒,去河邊找鬼堪簿。 笑死,一個胖子當(dāng)著我的面吹牛皮壁,可吹牛的內(nèi)容都是我干的椭更。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛾魄,長吁一口氣:“原來是場噩夢啊……” “哼虑瀑!你這毒婦竟也來了湿滓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舌狗,失蹤者是張志新(化名)和其女友劉穎叽奥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痛侍,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡朝氓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了主届。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膀篮。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岂膳,靈堂內(nèi)的尸體忽然破棺而出誓竿,到底是詐尸還是另有隱情,我是刑警寧澤谈截,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布筷屡,位于F島的核電站,受9級特大地震影響簸喂,放射性物質(zhì)發(fā)生泄漏毙死。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一喻鳄、第九天 我趴在偏房一處隱蔽的房頂上張望扼倘。 院中可真熱鬧,春花似錦除呵、人聲如沸再菊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纠拔。三九已至,卻和暖如春泛豪,著一層夾襖步出監(jiān)牢的瞬間稠诲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工诡曙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留臀叙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓价卤,卻偏偏與公主長得像劝萤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荠雕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容