配合Storyboard使用Autolayout
通过Storyboard或者XIB使用进行UI开发相对来说效率会高很多,特别是使用Autolayout的时候,通过Storyboard和XIB的设置更加直观,省去了大量的代码。
但是使用Storyboard和XIB不可避免会有一定的性能问题。毕竟Storyboard和XIB本身是一个文件,是一个xml文件,而且是在运行时加载,对于像启动页等这样对性能要求较高的地方会有一定的影响。特别是Storyboard文件,他能同时编写很多个页面,如果Storyboard文件比较大,加载消耗的性能越多。
但是Storyboard作为一个高效UI编辑工具,还是值得一用的。对于一些性能要求不高或者本身业务简单的app使用起来问题不大。当然业务比较复杂的app也不是不可用,只要做好模块化处理,页面的Storyboard划分出多个细小的模块,不但能减少多人同时开发一个大的Storyboard所带来的冲突问题,还能够在提高UI开发效率时减少一定的性能问题。苹果一直比较推荐使用Storyboard,相信以后也会越来越多的在Storyboard的缺点上进行提升,再加上以后硬件性能的不断提升,Storyboard所带来的的性能消耗影响会越来越小。所以还是可以放心使用的。
不同布局方式对比
1、直接设置frame
性能较好。但是不灵活,横竖屏切换适配、大屏小屏适配比较麻烦,开发效率较低。而且兼容性较差,设备屏幕是可能一直会改变的,每次改变都有可能新增适配。
2、直接使用原生代码实现Autolayout
性能较frame差,而且图层越复杂,性能影响越大。因为Autolayout在布局的时候需要设置各种约束,需要在布局前把各种约束计算出来,而且还要检测各种可能存在的约束冲突,才能得到预期的布局,这过程需要消耗一定的性能。开发效率也不高,原因在于代码量太大。但是在屏幕适配上比frame好很多,减少了后期维护的很多麻烦。
3、使用VFL格式化语言
VFL格式化语言是苹果为了针对直接原生代码实现Autolayout代码量过大的问题设计的。虽然可以减少使用Autolayout的代码量,但是语法使用起来不够直观,编写难度加大,不方便维护,不适合大量使用。在一些需要简化代码的局部视图使用还是可以的。比如开发SDK,既不想使用第三方源码避免冲突,又不想用XIB和Storyboard增加集成难度,这是用使用VFL简化原生Autolayout代码还是不错的。
4、使用SDAutoLayout
SDAutoLayout一个第三方代码,它是对frame及其相关属性的封装,通过点语法方式方便的设置frame,还能实现一定的动态布局。使用起来也比较方便,性能上相对Autolayout好很多。据说在使用上也很简单,改天体验一下再进一步分析。
5、使用Massonry
Massonry也是一个第三方代码,它是对原生Autolayout代码的封装,是基于约束的,通过一个block回调方便我们设置约束,代码编写效率比较高。毕竟是基于约束的,性能上相对SDAutoLayout较差。
6、使用Storyboard
使用Storyboard或者XIB配合着Autolayout的使用,虽然在开发上效率很高,但是性能上相对其他方式比较低。原因前面已经分析过了。
Autolayout的生命周期
参考自官方文档;
以下是Autolayout的生命周期示例图:
流程:约束发生变化后,布局引擎重新计算布局,计算完成后调用 superview.setNeedLayout(),将需要更新布局的视图进行标记,然后进入 Deferred Layout Pass,从上到下调用 layoutSubviews(),通过 Cassowary 算法计算各个子视图的位置,计算完成后将子视图的 frame 从 Layout Engine 里拷贝出来。
Deffered Layout Pass 即延迟布局阶段。主要有两步:
- 更新约束。由下往上(from subview to super view),从子视图到父视图,依次遍历视图层级,调用View的updateConstraints方法(或ViewController的updateViewConstraints方法)来更新约束,我们可以重写这个方法来设置自定义约束,且在此设置时,执行效率最高,且最后要调用父类实现;
- 设置views的frame。由上往下(from super view to subview)依次调用View的layoutSubViews方法或ViewController的viewLayoutSubViews方法,从Layout Engine中取出预算好的frame进行赋值。
延迟布局阶段的触发条件
setNeedsUpdateConstraints 下一次loop执行updateConstraints
updateConstraintsIfNeeded 立即执行updateConstraints
setNeedsLayout 下一次loop执行layoutSubViews
layoutIfNeeded 立即执行layoutSubViews
setNeedDisplay 下一次loop执行draw
Autolayout相关算法
Cassowary
1997年,一个名叫Cassowary的布局算法解决了用户界面的布局问题,它通过将布局问题抽象成线性等式和不等式约束来进行求解。
Cassowary能够有效解析线性等式系统和线性不等式系统,用来表示用户界面中那些相等关系和不等关系。基于此,Cassowary开发了一种规则系统,通过约束来描述视图间的关系。约束就是规则,这个规则能够表示出一个视图相对于另外一个视图的位置。
由于Cassowary算法让视图位置可以按照一种简单的布局思路来写,这些简单的相对位置描述可以在运行时动态的计算出视图具体的位置。视图位置写法简化,界面相关代码也就更易于维护。苹果公司也是看中了这一点,将其引入了自己的系统中。
Auto Layout 其实就是对 Cassowary 算法的一种实现。另外,求解线性规划问题的最具有代表性的算法之一,是Simplex。
Simplex
单纯形法是求解线性规划问题最常用、最有效的算法之一。单纯形法最早由 George Dantzig于1947年提出,近70年来,虽有许多变形体已经开发,但却保持着同样的基本观念。如果线性规划问题的最优解存在,则一定可以在其可行区域的顶点中找到。基于此,单纯形法的基本思路是:先找出可行域的一个顶点,据一定规则判断其是否最优;若否,则转换到与之相邻的另一顶点,并使目标函数值更优;如此下去,直到找到某最优解为止。
单纯形法的一般解题步骤可归纳如下:
(1)把线性规划问题的约束方程组表达成典范型方程组,找出基本可行解作为初始基本可行解 [7] 。
(2)若基本可行解不存在,即约束条件有矛盾,则问题无解 [7] 。
(3)若基本可行解存在,以初始基本可行解作为起点,根据最优性条件和可行性条件,引入非基变量取代某一基变量,找出目标函数值更优的另一基本可行解 [7] 。
(4)按步骤3进行迭代,直到对应检验数满足最优性条件(这时目标函数值不能再改善),即得到问题的最优解 [7] 。
(5)若迭代过程中发现问题的目标函数值无界,则终止迭代 [7] 。
用单纯形法求解线性规划问题所需的迭代次数主要取决于约束条件的个数。现在一般的线性规划问题都是应用单纯形法标准软件在计算机上求解 [7] 。
Autolayout性能问题
在使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就明确地(没有冲突)定义了整个系统的布局。
由于需要经过一系列的等式和不等式的计算,图形越多,约束越复杂,计算量越大。据说Simplex的时间复杂度是指数型增长的,这样图片越复砸,消耗的性能也是增速越大。
苹果也对Autolayout的算法进行一些优化,比如说增量更新,即只计算新增的约束,已经计算过得约束不再计算,这样在一定程度上能优化性能。但是仅仅是这样的话对于第一次加载视图层显然影响不大。当然苹果可能也还有其他优化,后期也有可能进行更多方面的优化吧。
Autolayout一些常用小技巧
Autolayout如何使用动画?
在需要添加动画的属性更新约束之后通过UIView的Block动画调用layoutIfNeeded。layoutIfNeeded是可以立即调用layoutSubviews重新布局的。
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
NSLog(@"Animation finish");
}];
如何高效修改Storyboard或者XIB的视图类型?
比如我们想修改UIImageView为UIbutton。最简单方法当然是把UIImageView删掉重新加一个UIbutton。但是这样做比较麻烦,还要重新设置约束等。这时可以通过Source code来修改,通过ObjectID找到对应的视图标签(xml语法),然后进行替换。替换时要注意不同视图属性不一样的地方,可以参考其他同类型标签进行修改。
动态删除和显示视图
比如下面的页面,需求可能需要根据条件变化删除任意一个条目然后其他位置补上。如下图:
通常我们的做法是通过修改Priority来实现,当然也可以通过修改active来实现。通过active来实现的话性能会更高一些,它把不需要的约束标记位deactive, 这样在本次循环计算布局时这个约束就会被忽略,不在计算。而如果用Priority就达不到这一点。