Optimising Autolayout

本文翻译自Martin Pilkington的文章Optimising Autolayout,也是本人翻译的第一篇技术文章。Florian Kugler在最近发表了一篇文章Autolayout Performance on iOS。这篇文章是关于Autolayout在添加视图所使用的时间,以及随着视图数量的增加,其所用时间的增长规律。这篇文章为我们提供了很多有用的信息,但是并没有很好的反应出Autolayout的真实性能,反而展示了一系列最差的情况。在这篇文章中,我将会更多的关注为什么Florian会得到这样的结果。我希望指出一些程序员可能会犯的、关于Autolayout的一些不好的习惯;并且关注Autolayout会花费数秒来布局几百个视图Autolayout可以快速地布局好几百个视图这两个看似截然相反的观点都正确的原因。##方法首先,我先阐明我是怎么样来处理上述的这些数字的。在我认为,这种方法与Florian的测量方式稍有不同,但是在展示布局方面却非常的相似。我在原来的程序基础上做了一些变动。你可以从我的GitHub上找到本文的项目。##原景再现因为我的这些方法以及使用的设备与Florian的有些不同,所用开始我会做一些与他一样的事情。他的项目测试了布局的三种形式:- 扁平视图的层次结构,全部视图布局在一个共同的根视图上- 扁平视图的层次结构,每个视图都相互约束着- 嵌套视图的层次结构,每个视图都相互约束着另外,Florian也做了只是上述两种层次结构下只是简单设置视图的frame的方式。下面的图表展示了我从扁平结构下得到的结果:

Optimising Autolayout_第1张图片
扁平式结构布局
若与Florian得到的结果比较,你会发现这两个结果截然不同。在Florian得到的图表中,绿线比橙线所表示的性能要差,但是几乎是差不多的;而在我得到的图表中,橙线的性能效果却更差(比如视图数量为600个时,尽管我的设备性能比Florian的更好,但是Florian只花费了5秒,而我的却接近于7.5秒。),但是绿线所表现的性能却比Florain的要好很多(比如同样在视图数量为600时,我得到的时间差不多为2.5秒,Florian得到的时间在6-7秒左右)。我认为造成这种不同的主要原因是因为测量的不同。在前面我提到,我测量的依据是方法方法创建约束的时间。为了做到这一点,我会在每个方法的最后在根视图上主动激活 -layoutIfNeeded方法。这就迫使Autolayout马上执行,而不是延后到当前 runloop结束的时候——这就意味着 Instrument计算的是创建约束的方法(method creating the constraints)的性能,而不是系统方法(system method)的系能。我认为Florian当时测量的是CPU的整个工作时间,但是这并不一定都是由于Autolayout所引起的。我认为我的方法更能表现Autolayout的行为,但是Florian的方法则更好的表现出来整个程序可能卡顿的时长。无论如何,但是真是的数据并不会像曲线这样夸张,以及任何我们可以找到的相关的提高性能的方法。
Optimising Autolayout_第2张图片
嵌套式结构布局
嵌套式结构的布局结果与Florian的结构几乎相同。曲线的吻合度几乎相同,唯一的不同就是我的运行结构稍微的快一点点,这与我运行在运行速度更快的设备上相关。##局部(小范围)的力量(The Power Of Locality)我在源代码中发现的其中一个内容就是,在源代码中,所有的约束都是加载同一个根视图上的。在某些场合下,约束与根视图相关,这种形式是必须的。与一个约束相关的所有视图必须在被加视图(即父视图)的视图子树(Subtree of the view)上。在这种情况下,你可以把所有的约束都添加到程序的根视图上。虽然你有很多原因不想这么做。很明显,把约束加在较小范围内会更容易理解。另一个原因就是,这种方法对于性能有很显著的影响。来看看我们的扁平式布局。当视图需要被约束在根视图上时(位置约束),它的尺寸约束却没有。我修改了代码,让视图的尺寸约束添加到其子视图上,并得到如下的结果:
Optimising Autolayout_第3张图片
扁平式布局
为了得到实际的数量,200个视图都加到根视图上时,布局花费了22.75秒,而把约束添加到最近的父视图时,布局却仅仅花了2秒钟。把相同的约束添加到根视图上会导致代码的运行速度慢11倍。结果很明确: 使用Autolayout布局时,尽可能地将所有约束都加在较小的范围内(locally)。##修改已存在的视图层级结构Florian在他的文章中提到,约束的满足问题设计到一个多元复杂度( polynomial complexity),我们也可以从上述图表的曲线中看出。但是,上面的测试根本不能代表现实中我们对Autolayout的使用。如果知道Autolayout在添加1000个视图到父视图有多快,这是非常有用的,就好比你知道NSArray添加数百万个对象有多快 一样。但是,大部分的NSArray很少有持有超过数百个内容的,很多持有的内容都少于十个。同样的,很少有单个视图持有超过40-50个子视图的,或者单个视图有一个视图层级达到20-30层视图这么深的(我认为这些数值基本上也是不可能达到的)。更符合实际情况的是,存在一个视图层,我们希望对一些视图做位移操作,或者是添加一些新的视图。基于此,我做了一些其他的测试。使用与之前一样尺寸的视图,并同样采用扁平式布局和嵌套式布局,然后计算出移动所有的视图以及添加10个新的视图所花费的时间。我们可以从下面的图表中可以看出,即便扁平式结构下,视图的数量达到了1000个,再添加10个视图,其位置布局所消耗的时间也基本是线性的。这是因为我只以根视图为参考,因此,其他视图并不需要重新进行计算。如果我们在一个位置相互约束的视图链中间插入一个视图,就有可能没有这么快了。同样,移动视图布局所消耗的时间也基本是线性的,不过,在视图数量达到1000个时,布局消耗的时间也的确是突然地增加了。同样,这是因为对于一个视图的约束并不依赖与其他任何的兄弟视图。

Optimising Autolayout_第4张图片
扁平式

再来看看看嵌套式结构,我们可以发现移动视图所消耗的时间也几乎是线性的。但是其线性的变化却比扁平式结构的更平缓,但是这正是这个图表的一个欺骗性的外表,他们其实基本上是一样的。当添加视图时,我们确实看到了一条曲线,但此时,我们是在添加一组是个视图的布局层结构,每个布局都与前一个相互约束着。
Optimising Autolayout_第5张图片
嵌套式

本文需要表达的事是与之前的测试相比,布局消耗的时间能有多快。添加1000个位置视图消耗了6.6秒,但是再添加10个视图却只消耗了0.055秒。这都归功于Cassowary Constraint Solver。这是一个递增的系统,不用每次都重头开始重新解决整个问题。它可以重复使用之前计算好的一切,当你做添加、编辑或移除约束时,仅仅只需要调整下结果即可。这就是为什么它会消耗数秒钟时间来一次性添加几百个视图的原因,但是你可以在这之后快速地重新调整窗口的尺寸,重新计算所有的约束和frame集合。Autolayout比手动设置视图的frame要慢一些。如果有特定的算法专门用于单个视图,将会执行的更快。Autolayout的优势并不在于在运行时使布局实现的更加快速,而是为了让我们在代码中能够更快速和方便的定义布局。与其他许多工具一样,Autolayout得益于现在设备强大的处理能力,使我们能够更轻松地来写App。在大多数情况下,如果使用得当,Autolayout将会非常快速的实现需求。这听起来可能会像是在狡辩,但这与我们使用更高级的编程语言而不是集成语言是一样的道理啊。

你可能感兴趣的:(Optimising Autolayout)