?? 今天寫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)invalidate
從RunLoop
中刪除并刪除之前綁定的 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)用也隨即停止荣恐,類似于NSTimer
的invalidate()
方法液斜。
(二)、 NSTimer的使用
- 創(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)
- 釋放方法
timer?.invalidate()
timer = nil
注意:調(diào)用創(chuàng)建方法后硼被,target對象的計數(shù)器會加1示损,直到執(zhí)行完畢,自動減1嚷硫。如果是循環(huán)執(zhí)行的話检访,就必須手動關(guān)閉,否則可以不執(zhí)行釋放方法仔掸。
- 特性
??存在延遲 脆贵,不管是一次性的還是周期性的timer的實際觸發(fā)事件的時間,都會與所加入的RunLoop
和RunLoop Mode
有關(guān)起暮,如果此RunLoop
正在執(zhí)行一個連續(xù)性的運算卖氨,timer
就會被延時出發(fā)。重復(fù)性的timer
遇到這種情況鞋怀,如果延遲超過了一個周期双泪,則會在延時結(jié)束后立刻執(zhí)行持搜,并按照之前指定的周期繼續(xù)執(zhí)行密似。
??使用上面的創(chuàng)建方式,會自動把timer加入MainRunloop
的NSDefaultRunLoopMode
中葫盼。如果使用以下方式創(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)")
}
}
}