iOS - CADisplayLink的介绍

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 对象,并给它提供一个 targetselector,当屏幕刷新的时候会调用。为了同步显示内容,需要使用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 timestampproperty 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 the maximumFramesPerSecond. To calculate the actual frame duration, use targetTimestamp - 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 to true. 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 the preferredFramesPerSecond 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增加显示文字动画效果

shine.gif
fade.gif
typewriter.gif
wave.gif

实现分析这里,Github

参考

CADisplayLink
Core Animation系列之CADisplayLink

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

推荐阅读更多精彩内容