前言
UITableView是iOS中使用最频繁的控件之一,其性能优化是我们经常要面对的,尤其是当数据量偏大并且设备性能不足时。
本文主要总结tableview性能优化的几个方法,如有不同思路或想法欢迎留言指正。
现状:很多情况下,每个cell高度的高度都不固定,是根据cell里面的内容自适应高度的。
问题:当cell根据内容自适应高度,每次渲染的时候,系统都会计算cell的高度(重复计算cell高度)。
构思:提前计算高度并实现缓存;减少了重复计算量,实现优化。
方案:
1、传统方法:为 cell 写个计算行高的类方法,传入那些动态的元素(文字,图片等),然后返回计算后的高度。在heightForRowAtIndexPath: 中调用这个方法,填入需要的参数计算cell 高度。这当然没有什么问题,只是要是计算量很复杂,你每次 reloadData ,光计算一行的高就要花去一点时间,想想有100行,还不定期的需要 reloadData 或者 insert(delete) row。【不是最优方案,pass…】
2、优化方法(空间换时间):将计算行高的时间提前到从服务器下载到数据的时候,计算完了高度一并写回数据库。简单一句理解就是,在取到服务器数据的瞬间,就计算好cell的高度并实现缓存(这是我的习惯用法)。
实操借鉴:
1、 优化UITableViewCell高度计算的那些事,Star很高。
2、TableView优化之高度缓存,借鉴上一篇来实现,思路简单清晰。
问题:cell对多张图片用maskToBounds进行圆角处理的话,帧数下降很快,容易造成卡顿。
原因:一次mask发生了两次离屏渲染和一次主屏渲染。即使忽略昂贵的上下文切换,一次mask需要渲染三次才能在屏幕上显示,这已经是普通视图显示3陪耗时,若再加上下文环境切换,一次mask就是普通渲染的30倍以上耗时操作。问我这个30倍以上这个数据怎么的出来的?当我在cell的UIImageView的实例增加到150个,并去掉圆角的时候,帧数才跌至28帧每秒。虽然不是甚准确,但至少反映mask这个耗时是无mask操作的耗时的数十倍的。
Off-Screen Rendering(离屏渲染)
指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。由上面的一个结论视图和圆角的大小对帧率并没有什么卵影响,数量才是伤害的核心输出啊。可以知道离屏渲染耗时是发生在离屏这个动作上面,而不是渲染。为什么离屏这么耗时?原因主要有创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。
上下文切换
上下文切换,不管是在GPU渲染过程中,还是一直所熟悉的进程切换,上下文切换在哪里都是一个相当耗时的操作。首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,如需要切换到On-Screen Rendering或者再开始一个新的离屏渲染重复之前的操作。 下图描述了一次mask的渲染操作。
构思:不使用或尽量少使用maskToBounds进行圆角处理
方案:
1、缓存视图渲染内容
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
这样大部分情况下可以马上挽救你的帧数在55帧每秒以上。shouldRasterize = YES可以使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,当然要在视图内容不改变的情况下。
2、预先生成圆角图片
采取预先生成圆角图片,并缓存起来这个方法才是比较好的手段。预处理圆角图片可以在后台处理,处理完毕后缓存起来,再在主线程显示,这就避免了不必要的离屏渲染了。
3、镂空圆形图片覆盖
此方法可以实现圆形头像效果,这个也是极为高效的方法。缺点就是对视图的背景有要求,单色背景效果就最为理想。
4、 Core Graphics API 绘制圆角
在willDisplayCell的方法里面我们把 cell的背景设置成透明,然后自定义 UIView 做为 cell 的 backgroundView,那如果需要选中状态的话,还需要再自定义一个 UIView 做为 cell 的 selectedBackgroundView,通过 Core Graphics API 来实现 UIView 图层圆角的绘制。
实操的时候,这里有几篇文章值得借鉴:
补充:UILabel实现圆角
1、不设置背景色(backgroundColor)类型:只设置borderWidth、borderColor的label,直接设置cornerRadius,不需要设置masksToBounds = YES,就可以实现圆角功能。
2、需要设置背景色(backgroundColor)类型:直接设置cornerRadius是不能正常显示圆角的,原因是:UILabel设置backgroundColor的行为,不再是设定layer的背景色而是为contents设置背景色。所以解决方式是我们不去设置label的backgroundColor,而是直接设置label.layer.backgroundColor,这样就可以实现单独设置cornerRadius,显示圆角的效果。代码:
UILabel *tagLabel = [UILabel new];
tagLabel.text = @"减";
tagLabel.textColor = [UIColor whiteColor];
tagLabel.font = [UIFont systemFontOfSize:12];
tagLabel.layer.backgroundColor = [UIColor greenColor].CGColor;
tagLabel.layer.cornerRadius = 2;
问题:subview层级太复杂,给cell在绘制时添加很大的负担
原因:层级太复杂,需要做大量透明处理。
构思:手动绘制视图提升流畅性。将一些相对重复性很大的视图选择使用CoreText和CoreGraphic技术直接绘制在一个视图上,减少视图的层级。
实操细节分析:
1、手动绘制方法,不是直接子类化 UITableViewCell,然后覆盖 drawRect: 方法,这样你会得到一个大黑块!因为 cell 中不是只有一个 content view。如果不了解 cell 的层次结构,可以用 Reveal 去看下。
2、绘制 cell 不建议使用 UIView,而是用 CALayer。 UIView 的绘制是建立在 CoreGraphic 上的,使用的是 CPU。CALayer 使用的是 Core Animation,CPU,GPU 通吃,由系统决定使用哪个。View的绘制使用的是自下向上的一层一层的绘制,然后渲染。Layer处理的是 Texure,利用 GPU 的 Texture Cache 和独立的浮点数计算单元加速纹理的处理。
3、GPU不喜欢透明,所以所有的绘图一定要弄成不透明,对于圆角和阴影这些的可以截个伪透明的小图然后绘制上去。在layer的回调里一定也只做绘图,不做计算!
实操借鉴:CoreText实现图文混排系列,进一步学习中。
问题: 图片通过contentMode处理显示,对tableview滚动速度同样会造成影响。
原因:图片变形需要对图片做 transform ,每次压缩图片都要对图片乘以一个变换矩阵,如果显示的图片很多,这个计算量是不同忽视的。
构思方案:
1、从网络下载图片后先根据需要显示的图片大小切/压缩成合适大小的图,每次只显示处理过大小的图片,当查看大图时在显示大图。
2、服务器直接返回预处理好的小图和大图以及对应的尺寸最好。
实操:
1、图片剪切压缩集成。
2、后台配合。
实操借鉴:
1、UIImage剪裁、压缩、拉伸等处理
2、iOS 图片压缩方法
问题:当TableView快速滚动时,仍然会出现加载跟不上占用系统资源,导致卡顿。
原因: 当TableView的cell中图片很多时,快速滚动页面,使用SDWebImage加载图片虽然是异步过程,但是仍然十分占用系统资源。
构思:快速滚动或滚动时,图片异步加载处理。
方案:
1、TableView滚动的时候不去加载未加载过的图片,停止滚动后再从网络加载(做好监听判断处理)。
2、TableView快速滚动的时候(做好监听判断处理)不加载未加载过的图片,当滚动速度小于某个区间值(不管是拖动中还是没有拖动,不管是加速还是减速中)的时候再从网络加载未加载过的图片。
注:已经加载过得图片,无论什么时候都加载该图片(因为SDWebImage会将加载过得图片缓存下来,再次加载的时候从缓存中取,这样就不用开辟线程下载图片了)
方案分析:
1、思路一:
优点:逻辑简单清晰,容易处理。
缺点:属于一种完全考虑数据优化的角度去处理问题,可以减少很多系统资源的占有,但是感觉缺少了用户体验,或者说是用户体验极差,只有在停止的时候才去加载图片,那么中间都是一片空白。
2、思路二
优点:将用户体验和图片异步加载减压两个方面都考虑在内,取一个相对比较合理的空间,实现两方面的优化。各取0.5,加起来会是大于1的效果。
缺点:逻辑复杂,处理麻烦,需要进行多次的调试测试。
实现基本思路:
1、写一张图片展示的表格
2、导入图片数组数据,实现加载渲染图片的方法。
3、监听表格滚动速度,设置合理滚动区间之内的判断(去加载未加载过的网络图片)
4、记录已加载的网络图片并判断。
5、表格停止滚动时/接触tableview时,加载当前显示cell未加载下来的图片,并渲染在cell上。
实操借鉴:
1、TableView优化之快速滑动下的忽略加载
2、TableView加载图片的优化逻辑
3、iOS 同一页面加载上百张图片,迅速滑动时导致内存暴涨程序崩溃的参考解决方法
方案二是根据前两篇的思路结合应用,最后一篇便是好的应用。
参考链接:
1、UILabel简单高效实现圆角的方式
2、【原/转】UITableview性能优化总结
发现一些更优秀的,还有好多需要学习:
1、老生常谈之UITableView的性能优化
2、UITableView性能优化,超实用