[ WWDC2018 ] - 高性能 AutoLayout High Performance Auto Layout

UICollectionView性能对比,item自动适配大小,iOS 11看上去有掉帧卡顿的现象,iOS 12表现完美,没有掉帧。

WX20180612-104339.png

下面是iOS 11和iOS 12的性能对比,灰色条是iOS 11的耗时,蓝色条是iOS 12的耗时。在iOS 12上会很大程度改善你的应用程序。

WX20180619-160559.png

实现和感观

render loop

render loop 是一个每秒钟跑120次的一个进程,是为了确保所有的内容都能为每一个frame做好准备。lender loop 一共包括三个步骤来更新约束,布局和渲染。

  • 首先,每一个需要接收到更新约束的view会从子view向上传递,直到window
  • 然后,每一个接收到的view开始layoutsubviews,和更新约束是从相反的方向开始,layout从window开始到每一个子view进行layout。
  • 最后,每一个需要渲染的view,和layout相同,从父view向子view开始渲染。
WX20180619-160634.png

render loop目的是为了避免重复的工作。
举一个例子:一个UILable 需要一个约束来描述它的大小,但是有很多属性会影响他的大小,设置它的font,text size等等都会受到影响。当一个属性改变的时候,可能text其他属性也会被重新赋值
,很有可能调用一堆属性的setter方法,这样效率会很低。
只需要调用updateConstraints 并指定好要更新的属性,render loop会帮助你计算好它的frame并完成渲染,从而避免多次设置的重复工作。


WX20180619-160709.png

在设置约束的一些不好的写法,每次开始的时候调用deactivate,设置结束之后调用activate。相当于layoutsubviews,每次调用layoutsubviews你销毁你subviews,重新创建在重新添加。这样性能不会很好。

// Don’t do this! Removes and re-adds constraints potentially at 120 frames per second
    override func updateConstraints() {
        NSLayoutConstraint.deactivate(myConstraints)
        myConstraints.removeAll()
        let views = ["text1":text1, "text2":text2]
        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                        options: [.alignAllFirstBaseline],
                                                        myConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                                                                        metrics: nil, views: views)
            options: [],
            metrics: nil, views: views)
        NSLayoutConstraint.activate(myConstraints)
        super.updateConstraints()
    }

每次都是移除并重新添加,相当于这样的代码

    // Don’t do this! Removes and re-adds constraints potentially at 120 frames per second
    override func layoutSubviews() {
        text1.removeFromSuperview()
        text1 = nil
        text1 = UILabel(frame: CGRect(x: 20, y: 20, width: 300, height: 30))
        self.addSubview(text1)
        
        text2.removeFromSuperview()
        text2 = nil
        text2 = UILabel(frame: CGRect(x: 340, y: 20, width: 300, height: 30))
        self.addSubview(text2)
        super.layoutSubviews()
    }

官方建议写法为,约束只需要添加一次,每次调用super.updateConstraints完成约束的更新。

    // This is ok! Doesn’t do anything unless self.myConstraints has been nil’d out
    override func updateConstraints() {
        if self.myConstraints == nil {
            var constraints = [NSLayoutConstraint]()
            let views = ["text1":text1, "text2":text2]
            constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-[text1]-[text2]",
                                                          options: [.alignAllFirstBaseline],
                                                          metrics: nil,
                                                          views: views)
            constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[text1]-|",
                                                          options: [],
                                                          metrics: nil,
                                                          views: views)
        }
        NSLayoutConstraint.activate(constraints)
        self.myConstraints = constraints
        super.updateConstraints()
    }

render loop有很强的特定性,它的好处可以避免一些重复性的工作。但是它也很危险,因为它调用的频率会很高,是非常敏感的一段代码。

苹果建议使用interface builder进行布局。


WX20180619-160810.png

激活一个约束

在设置约束的时候发生了什么事情呢?从下面的图中可以看到整体的一个结构。
有一个view 在window上,window上面有个叫做engine的内部对象,engine是autolayout计算的核心,当添加一个约束的时候,会创建一个Equation对象,然后会把equation对象添加到engine上,equation依据variables对象。


WX20180619-160848.png

variables相当于每一个约束的值,比如说一个UIlabel有四个约束minX minY width height那么minX minY width height 就是variables。


WX20180619-160919.png

以下面这个图为例,这里只关注水平方向的布局,首先要创建equation,然后每一个equation会添加给engine。


WX20180619-161418.png

engine会去计算这些variables,engine会把每一个view的variables用数学公式计算出一个定量。


WX20180619-161501.png

计算出定量之后,engine会发送通知,通知view调用他父view的setNeedsLayout()方法,就会完成render loop的第一步更新约束,然后继续render loop的 layout更新,最后view会直接拷贝engine计算好的定量进行赋值渲染。


WX20180619-161533.png

engine是一个layout的缓存,和依赖的追踪器。非常有方向性的,它知道哪些约束会影响哪些view,当你改变一些约束时,它能够准确的更新。

不需要的约束不要加

你也可以穿过层级,为两个没有相同父view的view设置约束,但是这样性能会很差。
大多数情况下,view的约束应该加在他的父view或者兄弟view上。


WX20180619-161603.png

最小限度的错误

当view向engine获取约束的值的时候,engine会确保错误率最小


WX20180619-161637.png

构建高性能layout

创建一个layout

构建一个社交软件的cell,通过autolayout进行布局。


WX20180619-161704.png

查找代码中的问题

下面是beta版的一个调试工具,最上面第一项表示你CPU的使用情况,峰值的地方可能需要关注一下你的layout是否有性能问题,下面一行追踪你的约束,高的地方说明是有问题的。
第二项是你对约束添加、删除、修改等操作的记录。
第三项是当前控件的大小。

WX20180619-161733.png

点击约束峰值的地方可以看详情。

创建高性能的布局

通过instrument调试工具,可以看出一些布局上的耗时问题。一下是需要注意的几点:

  • 避免删除所有的约束的情况
  • 对于静态约束,只需要添加一次
  • 只改变需要改变的约束
  • 尽量用hide() 方法隐藏view,而不是remove然后在add

有些控件比较特殊,比如 UIImageView,它的大小是根据他的image计算确定他的content size。UILabel是根据他的text确定的。这些都会返回它们的固有尺寸,UIView 会直接通过他们的固有尺寸来当做约束条件。

重写 intrinsicContentSize

text的计算是成本很高的,所以UIlabel的size通过text去控制计算开销成本会很高。这个时候我们可以 通过重写 UILabel 的 intrinsicContentSize 来直接控制它的固有尺寸。如果已知一个UILabel的展示size,直接重写其属性,其他情况使用UIView.noIntrinsicMetric。

override var intrinsicContentSize: CGSize {
    return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
}


参考:WWDC2018《High Performance Auto Layout》

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

推荐阅读更多精彩内容

  • 1.夜晚的诅咒 2.梦中的城堡 妈妈的突然消失让家里变得空荡荡的,原来每个角落都曾被妈妈忙碌的身影填满,即便她在兄...
    琉璃小兽阅读 310评论 0 0
  • (一) 人们在追求目标的同时往往容易忘记享受过程。高中生的目标是考大学,所以他们往往只看中分数,而...
    谭一隅阅读 831评论 0 0
  • 东风润烟雨, 莲心*明前煦; 茗茶千秋事, 独饮嫩黄绿。 注:中国绿茶分四个等级,其中“莲心”为一级,一般在明前采...
    写作匠人阅读 711评论 1 1
  • 看到几句话说的特别好 ①“在很多个‘我本可以’的时刻里,我都放过了。” ②不要在狼性的年纪选择做个俗人 ③所有被千...
    我不是小一班的阅读 54评论 0 0