计时器CADisplayLink

CoreAnimationXmind.png

这是 Core Animation 的系列文章,介绍了 Core Animation 的用法,以及如何进行性能优化。

  1. CoreAnimation基本介绍
  2. CGAffineTransform和CATransform3D
  3. CALayer及其各种子类
  4. CAAnimation:属性动画CABasicAnimation、CAKeyframeAnimation以及过渡动画、动画组
  5. 图层时间CAMediaTiming
  6. 计时器CADisplayLink
  7. 影响动画性能的因素及如何使用 Instruments 检测
  8. 图像IO之图片加载、解码,缓存
  9. 图层性能之离屏渲染、栅格化、回收池

1. 什么是CADisplayLink

CADisplayLink常被认为是一种创建平滑动画的高级计时器。这是CADisplayLink的一种应用,但不是唯一应用。

CADisplayLink的文档中,其描述如下:

A timer object that allows your application to synchronize its drawing to the refresh rate of the display.

CADisplayLink是一个计时器对象,允许应用将自身的绘制与屏幕的刷新率同步。

CADisplayLinkNSTimerDispatchSourceTimer有些相似,根据自身配置定期触发回调:

    private func createDisplayLink() {
        let displayLink = CADisplayLink(target: self, selector: #selector(self.linkTriggered(_:)))
        displayLink.add(to: .main, forMode: .default)
    }
    
    @objc private func linkTriggered(_ displaylink: CADisplayLink) {
        print(displaylink.timestamp)
    }

init(target:selector:)创建的 display link 会持有 target。

这里并未对 display link 做任何配置,直接添加到主线程的默认mode。你可以将CADisplayLink添加到任意线程 run loop 的任意 mode。需要注意的是:手动创建的线程、GCD 提供的后台线程默认没有 Run Loop,需手动启动 run loop。如果你对 run loop 不了解,可以查看我的另一篇文章:RunLoop从入门到进阶

CADisplayLink最大的特点就是与屏幕刷新帧率同步。它的回调由事件触发,而非时间,因此它更像是一个观察者,而非计时器。触发事件是刷新的帧。因此,CADisplayLink的回调方法在另一帧渲染后立即被调用。如果设备帧率是60FPS,回调方法不是每16.6ms调用一次(六十分之一秒约等于16.6ms),而是帧更新后调用。因此,有足够的16.6ms用来渲染下一帧。

TimerDispatchSourceTimer的触发与帧刷新(frame update)没有关联,具体取决于计时器的启动时间和tolerance,触发时可能距离下一帧更新16ms,也可能只有2ms。

2. 帧 Frame

iOS 中有多种方式管理 UI。可以使用UIKit框架的UIView动画更新视图;使用 Core Animation 操纵CALayer,执行CAAnimation;也可以更进一步使用更为底层的 CoreGraphics 的 context、shape 和 path;如果想更为精细的控制渲染,则可以使用 OpenGL、Metal。当需要使用 Core Graphics、OpenGL、Metal 手动绘制帧时,CADisplayLink会非常有用。

屏幕显示内容是由 CPU 和 GPU 协作的结果。CPU 计算帧内容,GPU 显示到屏幕中。CPU 和 GPU 异步协作,当 GPU 显示第一帧内容时,CPU 正在计算第二帧内容。CPU 计算完成前,GPU 没有新内容需要绘制,因此 GPU 会保留上一帧内容在屏幕上。

CACPUGPU.png

在上图中,可以看到帧如何切换。分隔每个框的垂直虚线是显示刷新,也是上一帧、下一帧切换点。第一行是 CPU 渲染帧,第二行是 GPU 显示帧。

2.1 流畅动画

CARegularCalculation.png

上图中的渲染内容很简单,每一帧都可以在一帧时间内渲染完成。在六帧的时间里,渲染了6帧内容,动画非常流畅。

2.2 部分掉帧

CAUnstableCalculation.png

在上图中,有些情况下一帧的时间足够渲染内容,有些则不够。当 CPU 没有按时渲染完成内容时,到达下一帧时间时,GPU 会保留上一帧内容在屏幕上。这样会导致有些帧在屏幕上时间超过预期时间,动画产生卡顿、掉帧现象。

2.3 严重掉帧

CAHeavyCalculation.png

上图显示了每一帧渲染时间都超过一帧时间,导致每一帧显示时间都大于一帧时间,最终在六帧的时间里只显示了三帧内容。

3. 时间相关属性

3.1 timestamp

timestampCFTimeInterval类型,关联值是上一帧渲染时间,也是这一帧计算开始时间。

3.2 targetTimestamp

targetTimestamp也是CFTimeInterval类型,它是下一帧开始时间。

可以使用targetTimestamp取消、暂停长时间执行的进程,以避免执行已经超出执行时间的任务,进而确保整体的流畅性。

linkTriggered(_:)回调方法内计算平方根的和。在每次计算后,比较CACurrentMediaTime()targetTimestamp,如果计算平方根时间大于等于targetTimestamp,则停止计算。

    @objc private func linkTriggered(_ displaylink: CADisplayLink) {
//        print(displaylink.timestamp)
        
        var sqrtSum = 0.0
        for i in 0 ..< Int.max {
            sqrtSum += sqrt(Double(i))
            
            if CACurrentMediaTime() >= displaylink.targetTimestamp {
                print("Break at i = \(i), sqrtSum:\(sqrtSum)")
                break
            }
        }
    }

3.3 duration

duration是两帧间隔:

        duration = displayLink.targetTimestamp - displayLink.timestamp

CADisplayLink首次回调前,duration值是未定义的。

3.4 preferredFramesPerSecond

preferredFramesPerSecond默认为0,display link 按照屏幕最大帧率回调。有时可能想要帧率低一些,例如16ms不能完成渲染,这时可通过设置preferredFramesPerSecond降低 display link 帧率。

之所以被称为 preferred,是因为 display link 预定义了一些帧率。如果UIScreenmaximumFramesPerSecond为60,则可以降低帧率为30FPS(每两帧调用一次)、20FPS(每三帧调用一次)、15FPS(每四帧调用一次)等等。设置偏好帧率后,系统尽可能满足偏好设置。例如,设置preferredFramesPerSecond为27FPS,系统会将帧率设置为最为接近的30FPS。

可以使用以下代码输出当前实际帧率:

        print("Actual frames per second: \(1 / (displaylink.targetTimestamp - displaylink.timestamp))")

设置preferredFramesPerSecond小于设备最大帧率后,duration并不会随之改变。duration是帧之间间隔,并非回调间隔。如果想要计算回调间隔,使用以下代码:

        displaylink.targetTimestamp - displaylink.timestamp

4. 销毁 CADisplayLink

有两种控制CADisplayLink生命周期的方式,一种是暂停,一种是销毁:

        displayLink.isPaused = true
        displayLink.invalidate()

isPaused默认为 false。设置为 true 后,不再向 target 发送通知。isPaused是线程安全的。

invalidate()会将 display link 从所有 run loop 移除,run loop 也会释放 display link,display link 同时会释放 target。invalidate()是线程安全的,即可以在 display link 外的线程执行。

不再使用 display link 时,需显式销毁。即使在回调中没有执行任何工作,一秒回调60次也会占用 CPU 时间,消耗设备电量。

可以使用remove(from:forMode:)方法将指定 display link 从指定 mode 移除。如果移除后 display link 没有关联到任何 mode,run loop 会释放掉 display link。

5. 哪种情况下使用 CADisplayLink

CADisplayLink是将 model 连接到 UI 的一种方式。

5.1 设备

大部分 iPhone 都是60Hz,iPad pro 是120Hz。因此,maximumFramesPerSecond是一个变量,不应作为常量60使用。应忽略设备硬件,使用时获取UIScreenmaximumFramesPerSecond,或CADisplayLink的属性。

        print("maximumFramesPerSecond:\(UIScreen.main.maximumFramesPerSecond)")

5.2 Model

在处理复杂模型时,CADisplayLink会很有用。例如,处理游戏、视频的复杂模型,同时添加滤镜、快速变动的状态。如果内容提供者数据变动极快,如每秒100、200次,可以使用CADisplayLink降低到30、60 fps等。在涉及电脉冲的医学或物理测量领域,这是很常见的情况。

5.3 UI

使用CADisplayLink时需格外注意,它带来的危害可能大于收益。

很多时候不需要与刷新率一致的精准度,有很多更高性能的实现方式。例如:UIView-animation、CAAnimationUIKitDynamicsCADisplayLink每秒回调多次,即使回调中并没有执行任何工作,也会占用 CPU 时间和损耗电池电量。

如果需要立即处理状态变化,即使毫秒级延迟也会产生影响,这时请使用CADisplayLink。但你可能不需要120fps、60fps的帧率,记得使用preferredFramesPerSecond设置合适帧率。

Demo名称:CoreAnimation
源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/CoreAnimation

上一篇:图层时间CAMediaTiming

下一篇:影响动画性能的因素及如何使用 Instruments 检测

参考资料:

  1. CADisplayLink
  2. CADisplayLink and its applications

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/计时器CADisplayLink.md

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容