这是一个老生常谈的话题,程序猿的一生除了在编译、写bug、改bug,就是在不断地重构优化,优化的点也多种多样,其中最直观有效的就是UI部分的优化了,这方面有很多相关资料,也有如YYkit、Texture这种神级框架从根源上解决了卡顿的问题。作为菜鸟的我无法像大佬们一样从根源上分析解决UI性能问题,只能写一篇比较基础的文章,总结一下常用的控件的使用注意点,希望对你的日常开发有帮助~
基本法
先来一波UI操作的基本法则:
- 尽量减少在主线程中的操作,避免阻塞主线程。执行的操作越多就意味着越高的丢帧率,从而导致丢帧卡顿。
- 尽量保持视图的扁平化,避免视图的多层嵌套。
只要给视图添加子视图,或其子视图布局发生变化都会触发父视图的layoutSubviews
方法,如果一个视图被塞了很多子视图,性能消耗可想而知。那么这种情况如何避免呢?最好的方法是自定义视图绘制。这样只会触发一个视图的绘制方法,而不是多个子视图的,同时避免了父视图多次调用layoutSubviews
和drawRect:
方法,具体操作文中会给出demo。 - 尽量使用不透明视图。
当一个view是透明的,iOS需要渲染一个像素两次或多次,这是因为一个像素同时属于很多subviews。这是一个非常耗时的过程。不透明的视图可以极大地提高渲染的速度。因此如非必要,可以将table cell及其子视图的opaque
属性设为YES(默认值)。 - 一些情况下避免使用具有通用目的及功能丰富的控件,系统控件虽然方便,但有时也会带来不必要的消耗,你可以通过封装视图绘制内容来代替。如只显示大段纯文本,那么不必使用功能复杂的
UILabel
。 - 尽量延迟加载、重用复杂视图。
这点我相信大家在使用UITableView
、UICollectionView
时都深有体会,如果你对机制足够了解,那么在使用UIScrollView
时也可以尝试使用重用机制。 - 尽量避免出现较大的xib或storyboard。sb固然强大,但整个XML文件在使用之前必须被解析和加载,所以应该最小化sb中的单元数目,创建多个sb或xib,这样不仅有助于减少应用启动时间,还能降低整体内存消耗。
聊完基本法,我们来聊一下几个常见视图,看看有哪些性能小技巧。
UILabel
这绝对是你用的最多的控件,看似简单,但你真的了解它的性能消耗点吗?先来看看他有多复杂。
图中的每一项设置都会增加UILabel
的渲染代价,具体步骤如下:
1.使用字体样式及要被渲染的文本时,计算需要的像素数,这是一个消耗较大的过程,应尽量少做。
2.检查要被渲染的宽度。
3.检查numberOfLines
,计算将要展示的行数。
4.sizeToFit
是否被调用,调用则计算高度,未调用则检查当前尺寸是否能展示完整内容。
5.如果size不够展示,则使用lineBreakMode
确定隐藏或截断的位置。
6.检查其他配置选项,如纯文本or富文本,富文本的样式,对齐方式,自动收缩等。
7.最后使用字体、类型及颜色等渲染最终显示的文本。
整个过程下来的渲染代价不容小觑,所以平时使用要避免一些不必要的性能浪费。
ps:有时会发现label的文字变模糊,那么你需要检查一下label的frame是否都为整数。
UIButton
按钮同样无处不在,样式多变、功能强大的它渲染方式主要有以下四种:
- 使用自定义文本默认渲染
- 全尺寸资源渲染
- 可变尺寸资源渲染
- 使用
CALayer
和贝塞尔曲线自定义绘制
下表简单列出了几种渲染方式的利弊。具体见Designing for iOS: Taming UIButton。
渲染方式 | 优点 | 缺点 |
---|---|---|
自定义文本 | 简单易用 | 样式单一 |
全尺寸资源 | 可自定义背景图 样式多变 可实现A/B测试 | 图片导致ipa包变大 |
可变尺寸资源 | ipa包大小增量较全尺寸小 | 资源的任何更改可能都会重新计算UIEdgeInsets |
自定义绘制 | 高度自定义样式 | 随着迭代代码回越发臃肿 |
通过上面的比较,你需要权衡一下利弊,到底是要性能能还是要ipa包保持合适的大小。
UIImageView
图像的使用也很简单,但在渲染代价较大的UI元素中,图像首屈一指。在使用UIImage、UIImageView时,注意以下几点有助于性能提升:
- 加载图片的方式有三种
imageNamed:
、imageWithContentsOfFile:
、imageWithData:
,正确选择图片加载方式能够对内存优化起到很大的作用。imageNamed:
优点在于可以缓存已经加载的图片,确保内容只被加载至内存一次,这对于图像的重复利用是非常有优势的。对于常用的小图推荐使用该方法,可以节省出每次都从磁盘加载图片的时间,这样能更好的响应用户的操作。imageWithContentsOfFile:
和imageWithData:
这两种方法的本质是一样的, 都是简单的加载图片,并不会将图片缓存起来,图像会被系统以数据方式加载到界面。一些不常用的大图推荐使用这两种方法,这样就不会缓存这些图片占用内存。 - 对于网络图像,使用高性能的图像缓存库,如YYWebImage、SDWebImage。
- 在图片格式的选择上同样很有讲究,选择合适的格式能够为你的应用带来最佳体验,这里奉上YY大神的博文移动端图片格式调研。
- 载入的图片与
UIImageView
尺寸相同。因为调整图片尺寸是一个性能消耗较大的操作,如果图像在UIScrollView
中,则消耗更大。如果图片来自网络下载,那么尽量做到下载的图片与视图尺寸匹配,或者对图片进行预处理,调整尺寸。 - 在非主线程中解压JPG/PNG图片,最好在一个专用队列中执行。
- 在一些界面的实现上,确定是否真的需要图片。如展示一个评分控件,那么最好通过直接绘制,调整透明度或覆盖来实现,而不是使用多张图片。类似情况的取舍既可以优化性能,还能给安装包瘦身。