什么是 CADisplaylink?
对于什么是 CADisplaylink. 我们先来看看苹果官方文档中的描述:
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
从中可以看出, CADisplaylink 是一个计时器对象,可以使用这个对象来保持应用中的绘制与显示刷新的同步。更通俗的讲,电子显示屏都是由一个个像素点构成,要让屏幕显示的内容变化,需要以一定的频率刷新这些像素点的颜色值,系统会在每次刷新时触发 CADisplaylink。
CADisplaylink 的使用方法
使用 CADisplaylink 时需要先用一个 target 和 一个 selector 来创建一个 display link 对象,然后把创建的对象加到 runloop 中,代码如下:
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered)];
[displayLink setPaused:YES];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
CADisplayLink 对象一旦加入 Runloop 中,则会在屏幕需要刷新时回调 selector。如果要暂停对 selector 的调用,可以把 paused 属性设置为 YES 来实现。当不再使用 CADisplayLink 时,需要调用 invalidate 方法从所有的 Runloop 中将其移除。
在 selector 中可以通过 CADisplayLink 对象的属性 duration、frameInterval 和 timestamp 获取帧率和时间信息。关于他们的使用将在下面的实例中阐述。
应用之自定义动画
UIKit 和 Core Animation framework 中已经为开发者实现各式动画提供了丰富的接口。这些接口不仅可以实现简单的平移、旋转、切变、拉伸或者他们的组合,还可以控制这些动画发生的时间函数(Timing Functions)。
当动画中元素在空间上的位置变化或者在时间上的速度变化不能使用苹果提供的简单基本变换来叠加模拟时,此时我们就需要使用更复杂的接口来实现,如使用关键帧动画接口或者自定义时间函数。对于某些特别的动画,则可以借助 Display Link 来实现,比如下面的动画:
初看这个动画并不复杂,但是设计师重点强调了图标下落时要有重力的感觉,所以图标下落和弹起的速度是有变化的,图标本身在竖直方向上也有因为重力和碰撞产生的压缩和舒张,这些模拟真实物理特性的效果要使用苹果提供的基本变换和时间函数来实现将会花费很多时间去调试各种参数才能达到设计师一模一样的效果。鉴于设计师已经提供了 avi 格式的动画视频,并且动画中元素位置的变化也比较简单,于是笔者决定利用 Matlab 提取动画视频里各帧中物体的位置信息,然后在 selector 中根据这些位置信息更新 view 或 layer 的约束或者 frame 即可。
此方案无需考虑动画中元素的运动细节,实现起来比较简单,最终效果也与所给动画视频一致,但同时也有其固有的缺陷:首先是只有简单的特殊的动画效果可以使用此方案,原因在于其关键的部分在于使用 Matlab 提取动画的关键帧数据,这一操作只能对动画元素少、元素变换简单的动画实施;其次,如果动画效果有修改,则需要重新提取关键帧数据,这一点给动画的修改和调试带来不便;同时,如果设计师对动画有较大的修改,此方案也可能不再适用。
值得注意的是,苹果文档中还提到:如果应用不能及时提供显示帧,则应该降低帧率,因为较低的但是连贯的帧率要比高帧率但是存在掉帧看起来更顺滑一些。可以通过增大 frameInterval 这一属性的值来降低动画帧率。frameInterval 默认值为 1,表示每隔多少帧回调一次 selector。在没有卡顿时,iOS 设备屏幕显示每秒刷新60次,意味着 frameInterval 为默认值时,每秒回调60次 selector,当frameInterval 改为2时,每秒回调30(60/2)次 selector。
应用之帧率指示器
应用界面是否流畅是用户体验中十分重要的一方面,而帧率(FPS)是界面是否流畅的数字化指标,虽然可以通过 Instruments 查看到一些信息,但因操作路径较长,实际使用较少。
为了随时都可以直观的看到应用当前的帧率,可以给应用加一个帧率指示器。为了达到随时能看到的效果,我们把这个指示器放在一个特别的 Window 中,设置这个 Window 的 windowLevel 比应用中其他 Window 的 windowLevel 都要高。同时,为了减少对应用正常操作的影响,这个特别的 Window 只覆盖 statusBar 的一部分。
其实,苹果的官方文档中明确提到利用 CADisplaylink 可以计算显示的帧率:
The duration property provides the amount of time between frames. 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 的值应该是1/60,但是当主线程被阻塞或者应用在刷新时没有在有限时间内完成必要的操作都会导致 duration 值的增加,通过每一帧的 duration 值即可计算出实际帧率。
此方案实现起来并不复杂,在Github中也可以找到很多类似实现,例如 RRFPSBar, 感兴趣的读者可自行前往查看。值得一提的是,帧率显示本身也会占用一定的资源并影响实际的帧率,所以不宜在实现中做过多的操作。
总结
CADisplaylink 与 NSTimer 非常类似,都可以以一定的时间间隔触发回调 selector,不同点在于 CADisplaylink 的时间间隔是与屏幕的刷新频率相关联的,这一点决定了 CADisplaylink 的应用多与显示有关。