UITableView性能优化分析总结

简介: UITableView是iOS中使用最频繁的控件之一,其性能优化是我们经常要面对的,尤其是当数据量偏大并且设备性能不足时。

前言


UITableView是iOS中使用最频繁的控件之一,其性能优化是我们经常要面对的,尤其是当数据量偏大并且设备性能不足时。

本文主要总结tableview性能优化的几个方法,如有不同思路或想法欢迎留言指正。


优化要点:

一、cell高度计算优化

二、cell中圆角处理优化

三、复杂层级处理

四、图片尺寸处理优化

五、图片加载时间段优化


一、cell高度计算优化

现状:很多情况下,每个cell高度的高度都不固定,是根据cell里面的内容自适应高度的。

问题:当cell根据内容自适应高度,每次渲染的时候,系统都会计算cell的高度(重复计算cell高度)。

构思:提前计算高度并实现缓存;减少了重复计算量,实现优化。

方案:

1、传统方法:为 cell 写个计算行高的类方法,传入那些动态的元素(文字,图片等),然后返回计算后的高度。在heightForRowAtIndexPath: 中调用这个方法,填入需要的参数计算cell 高度。这当然没有什么问题,只是要是计算量很复杂,你每次 reloadData ,光计算一行的高就要花去一点时间,想想有100行,还不定期的需要 reloadData 或者 insert(delete) row。【不是最优方案,pass…】

2、优化方法(空间换时间):将计算行高的时间提前到从服务器下载到数据的时候,计算完了高度一并写回数据库。简单一句理解就是,在取到服务器数据的瞬间,就计算好cell的高度并实现缓存(这是我的习惯用法)。

实操借鉴:

1、 优化UITableViewCell高度计算的那些事,Star很高。

2、TableView优化之高度缓存,借鉴上一篇来实现,思路简单清晰。


二、cell中圆角处理优化


问题: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 图层圆角的绘制。


实操的时候,这里有几篇文章值得借鉴:


1、iOS 设置tableView每个分区cell圆角

2、修改 UITableView 的 UITableViewCell 圆角和 Section 圆角

3、tablviewcell中圆角处理 离屏渲染问题

补充: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性能优化,超实用


相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
7月前
|
缓存 前端开发 网络协议
性能优化|几个方法让图片加载更快一些
对电商网页的性能而言,图片优化是至关重要的事情,本文就此探讨了一些简单、可靠的图片优化手段。
|
7月前
|
设计模式 JavaScript 前端开发
说说如何使用事件委托进行性能优化
说说如何使用事件委托进行性能优化
|
10月前
|
JavaScript API 调度
requestAnimationFrame在性能优化中的应用有哪些?
【5月更文挑战第29天】requestAnimationFrame在性能优化中的应用有哪些?
133 1
|
前端开发
一次性能优化思考过程
最近业务上空闲了下来,也是把之前在开发时自身感受比较大的白屏时间放在了主线上去排查优化,这里记录一下笔者对于移动端vConsole脚本的引入问题全过程。
184 0
一次性能优化思考过程
|
SQL 缓存 NoSQL
服务性能优化总结
服务性能优化总结
148 0
|
存储 缓存 编解码
「性能优化系列」不使用第三方库,Bitmap的优化策略
在Android3.0到Android7.0,Bitmap对象和像素都是放置到Java堆中,这个时候即使不调用recycle,Bitmap内存也会随着对象一起被回收。虽然Bitmap内存可以很容易被回收,但是Java堆的内存有很大的限制,也很容易造成GC。 在Android8.0的时候,Bitmap内存又重新放置到了Native中。 Bitmap造成OOM很多时候也是因为对Bitmap的资源没有得到很好的利用,同时没有做到及时的释放。
404 0
|
存储 缓存 开发工具
UITableView性能优化-中级篇
老实说,UITableView性能优化 这个话题,最经常遇到的还是在面试中,常见的回答例如: Cell复用机制 Cell高度预先计算 缓存Cell高度 圆角切割 等等. . .
803 0
UITableView性能优化-中级篇
|
前端开发 索引
前端css性能优化
1. 避免使用@important 外部的css文件中使用@important会使得页面在加载时增加额外的延迟。最好使用link   2. 避免使用css表达式(表达式可能会造成极大的计算量)   3. 避免通配选择器 在初期使用*{margin:0;padding:0},以此来消除标签的默认布局和不同浏览器的对同一个标签的不同的渲染。
980 0