APP首页之UITableView的性能优化---缓存策略

###优化缘由:


项目虽然上线了,但是在程序性能方面仍然存在许多不足,其中之一就是:iOS端首页列表在滚动时页面的卡顿,这对用户体验来说是很不友好的,对其进行优化俨然成为了优先级较高的项目任务。

###优化方案调研:
众所周知,在iOS开发中UITableView是最常用到的一个UI类之一,几乎所有的APP都会用到,但是用好它却不是那么容易。对于它的优化方案也是多种多样,常见的大概有以下几种:

####预排版
当获取到 API JSON 数据后,把每条 Cell 需要的数据都在后台线程计算并封装为一个布局对象 CellLayout。CellLayout 包含所有文本的 CoreText 排版结果、Cell 内部每个控件的高度、Cell 的整体高度。每个 CellLayout 的内存占用并不多,所以当生成后,可以全部缓存到内存,以供稍后使用。这样,TableView 在请求各个高度函数时,不会消耗任何多余计算量;当把 CellLayout 设置到 Cell 内部时,Cell 内部也不用再计算布局了。对于通常的 TableView 来说,提前在后台计算好布局结果是非常重要的一个性能优化点。为了达到最高性能,你可能需要牺牲一些开发速度,不要用 Autolayout 等技术,少用 UILabel 等文本控件。但如果你对性能的要求并不那么高,可以尝试用 TableView 的预估高度的功能,并把每个 Cell 高度缓存下来。这里有个来自百度知道团队的开源项目可以很方便的帮你实现这一点:FDTemplateLayoutCell。

####预渲染
对于 TableView 来说,Cell 内容的离屏渲染会带来较大的 GPU 消耗。为了避免离屏渲染,你应当尽量避免使用 layer 的 border、corner、shadow、mask 等技术,而尽量在后台线程预先绘制好对应内容。

####异步绘制
当 TableView 快速滑动时,会有大量异步绘制任务提交到后台线程去执行。但是有时滑动速度过快时,绘制任务还没有完成就可能已经被取消了。如果这时仍然继续绘制,就会造成大量的 CPU 资源浪费,甚至阻塞线程并造成后续的绘制任务迟迟无法完成。较好的做法是:尽量快速、提前判断当前绘制任务是否已经被取消;在绘制每一行文本前,调用 isCancelled() 来进行判断,保证被取消的任务能及时退出,不至于影响后续操作。目前有些第三方微博客户端(比如 VVebo、墨客等),使用了一种方式来避免高速滑动时 Cell 的绘制过程,相关实现见这个项目:VVeboTableViewDemo。它的原理是,当滑动时,松开手指后,立刻计算出滑动停止时 Cell 的位置,并预先绘制那个位置附近的几个 Cell,而忽略当前滑动中的 Cell。这个方法比较有技巧性,并且对于滑动性能来说提升也很大,唯一的缺点就是快速滑动中会出现大量空白内容。如果你不想实现比较麻烦的异步绘制但又想保证滑动的流畅性,这是个不错的方案。

###我的优化方案
在查了大量的资料,看了很多UITableView性能优化相关的博客,跑了好几个demo后,根据当前项目的需求,我决定采用缓存策略来优化首页列表的卡顿。我的思路是:用cell计算后的高度作为value,cell对应model的id作为key,将高度缓存至内存中,以供cell再次需要获取高度时使用。

###优化后的成效
可以对比下采用缓存策略后,通过Instruments可以检测出cell在使用高度缓存前后页面滚动时帧率的变化:

使用前:

使用后: