什么是 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];
添加进runloop的时候我们应该选用高一些的优先级,来保证动画的平滑。可以设想一下,我们在动画的过程中,runloop被添加进来了一个高优先级的任务,那么,下一次的调用就会被暂停转而先去执行高优先级的任务,然后在接着执行CADisplayLink的调用,从而造成动画过程的卡顿,使动画不流畅。
duration属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间。我们可以使用这个时间来计算出下一帧要显示的UI的数值。但是 duration只是个大概的时间,如果CPU忙于其它计算,就没法保证以相同的频率执行屏幕的绘制操作,这样会跳过几次调用回调方法的机会。
frameInterval属性是可读可写的NSInteger型值,标识间隔多少帧调用一次selector 方法,默认值是1,即每帧都调用一次。如果每帧都调用一次的话,对于iOS设备来说那刷新频率就是60HZ也就是每秒60次,如果将 frameInterval 设为2 那么就会两帧调用一次,也就是变成了每秒刷新30次。
CADisplayLink 对象一旦加入 Runloop 中,则会在屏幕需要刷新时回调 selector。如果要暂停对 selector 的调用,可以把 paused 属性设置为 YES 来实现。当不再使用 CADisplayLink 时,需要调用 invalidate 方法从所有的 Runloop 中将其移除。
在 selector 中可以通过 CADisplayLink 对象的属性 duration、frameInterval 和 timestamp 获取帧率和时间信息。关于他们的使用将在下面的实例中阐述。
应用之帧率指示器
应用界面是否流畅是用户体验中十分重要的一方面,而帧率(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 的应用多与显示有关。
CADisplayLink、NSTimer使用注意
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。
CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 ,解决方案 使用block
方法1
方法2使用代理对象(NSProxy)