CADisplayLink
A timer object that allows your application to synchronize its drawing to the refresh rate of the display.
CADisplayLink是一个定时器,允许你的应用以和屏幕刷新率相同的频率将内容显示到屏幕上。
Your application initializes a new display link, providing a target object and a selector to be called when the screen is updated. To synchronize your display loop with the display, your application adds it to a run loop using the
add(to:forMode:)
method.
在应用中创建一个新的 CADisplayLink 对象,并给它提供一个 target
和selector
,当屏幕刷新的时候会调用。为了同步显示内容,需要使用add(to:forMode:)
方法把它添加到runloop
中
Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated. The target can read the display link’s
timestamp
property to retrieve the time that the previous frame was displayed.
一但 CADisplayLink
以特定的模式注册到runloop
之后,每当屏幕内容需要刷新的时候,runloop
就会调用CADisplayLink
绑定的target
上的selector
,这时target
可以读到 CADisplayLink
的每次调用的时间戳,该时间戳是上一帧显示的时间,这样可以用来准备下一帧显示需要的数据。
For example, an application that displays movies might use the timestamp to calculate which video frame will be displayed next. An application that performs its own animations might use the timestamp to determine where and how displayed objects appear in the upcoming frame.
例如应用中播放视频,可能使用时间戳来计算下一帧要显示的视频数据。应用能够利用时间戳来确定下一帧在哪里显示对象。
The
duration
property provides the amount of time between frames at themaximumFramesPerSecond
. To calculate the actual frame duration, usetargetTimestamp
-
timestamp
. You can use this value in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.
duration
属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间。为了计算实际的帧率时间,应该使用targetTimestamp - timestamp
??梢允褂谜飧鲋道醇扑阒÷实南允荆唇频募扑愠鱿乱恢∫允镜闹?。
Your application can disable notifications by setting the
isPaused
property totrue
. Also, if your application cannot provide frames in the time provided, you may want to choose a slower frame rate. An application with a slower but consistent frame rate appears smoother to the user than an application that skips frames. You can define the number of frames per second by setting thepreferredFramesPerSecond
property.
我们可以通过isPaused
属性是控制计时器暂停与恢复,能够控制通知是否失效,如果应用不能在指定的时间提供帧,你可能想要较慢的帧率,可以通知preferredframespersecond属性来控制每秒的帧数
When your application finishes with a display link, it should call
invalidate()
to remove it from all run loops and to disassociate it from the target.
当我们想结束一个CADisplayLink的时候,应该调用invalidate()
方法,从runloop中删除并删除之前绑定的 target和selector
CADisplayLink should not be subclassed.
另外CADisplayLink
不应该被子类化。
属性
/* The current time, and duration of the display frame associated with
* the most recent target invocation. Time is represented using the
* normal Core Animation conventions, i.e. Mach host time converted to
* seconds. */
open var timestamp: CFTimeInterval { get }
open var duration: CFTimeInterval { get }
/* The next timestamp that the client should target their render for. */
@available(iOS 10.0, *)
open var targetTimestamp: CFTimeInterval { get }
/* Defines how many display frames must pass between each time the
* display link fires. Default value is one, which means the display
* link will fire for every display frame. Setting the interval to two
* will cause the display link to fire every other display frame, and
* so on. The behavior when using values less than one is undefined.
* DEPRECATED - use preferredFramesPerSecond. */
@available(iOS, introduced: 3.1, deprecated: 10.0, message: "preferredFramesPerSecond")
open var frameInterval: Int
/* Defines the desired callback rate in frames-per-second for this display
* link. If set to zero, the default value, the display link will fire at the
* native cadence of the display hardware. The display link will make a
* best-effort attempt at issuing callbacks at the requested rate. */
@available(iOS 10.0, *)
open var preferredFramesPerSecond: Int
表示屏幕显示的上一帧的时间戳,这个属性通常被target用来计算下一帧中应该显示的内容
表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:时间=duration×frameInterval
下一帧显示的时间
由上面可知,就是在iOS10之后替换了frameInterval。指定了间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。当该值被设定小于1时,结果是不可预知的
使用方式
var displaylink: CADisplayLink?
func createDisplayLink() {
// 创建CADisplayLink
displaylink = CADisplayLink(target: self,
selector: #selector(step))
displaylink!.add(to: .current,
forMode: .defaultRunLoopMode)
}
@objc func step(displaylink: CADisplayLink) {
// 打印时间戳
print(displaylink.timestamp)
}
// 停止CADisplayLink
func stopDisplaylink() {
displaylink?.invalidate()
displaylink = nil
}
当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于NSTimer被启动了;执行invalidate操作时,CADisplayLink对象就会从runloop中移除,selector调用也随即停止,类似于NSTimer的invalidate方法。
CADisplayLink 与 NSTimer
- 原理不同
CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。
- 周期设置方式不同
iOS设备的屏幕刷新频率(FPS)是60Hz,因此CADisplayLink的selector默认调用周期是每秒60次,这个周期可以通过frameInterval属性设置,CADisplayLink的selector每秒调用次数=60/frameInterval。比如当frameInterval设为2,每秒调用就变成30次。因此,CADisplayLink周期的设置方式略显不便。
NSTimer的selector调用周期可以在初始化时直接设定,相对就灵活的多。
- 精确度不同
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在忙于别的调用,触发时间就会推迟到下一个runloop周期。更有甚者,在OS X v10.9以后为了尽量避免在NSTimer触发时间到了而去中断当前处理的任务,NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间范围。
- 使用场合
从原理上不难看出,CADisplayLink使用场合相对专一,适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。
实战
FPS 指示器
class FPSLabel: UILabel {
var displayLink: CADisplayLink!
var lastTimeStamp: CFTimeInterval = -1.0
var countPerFrame: Double = 0.0
override init(frame: CGRect) {
super.init(frame: frame)
displayLink = CADisplayLink(target: self, selector: #selector(displayLink(_:)))
displayLink.add(to: RunLoop.main, forMode: .commonModes)
self.font = UIFont.systemFont(ofSize: 14)
self.backgroundColor = UIColor(red: 60.0 / 255.0,
green: 145.0 / 255.0,
blue: 82.0 / 255.0,
alpha: 1.0)
self.textColor = UIColor.white
self.textAlignment = .center
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func displayLink(_ sender: CADisplayLink){
guard lastTimeStamp != -1.0 else {
lastTimeStamp = displayLink.timestamp
return
}
countPerFrame += 1
let interval = displayLink.timestamp - lastTimeStamp
guard interval >= 1.0 else {
return
}
let fps = Int(round(countPerFrame / interval))
self.text = "\(fps)"
countPerFrame = 0
lastTimeStamp = displayLink.timestamp
}
deinit {
displayLink.remove(from: RunLoop.main, forMode: .commonModes)
}
}
UILabel动画效果
使用CADisplayLink定时刷新给UILabel增加显示文字动画效果