关于页面的性能优化,我们能做些什么?个人想了一下,能想到的就是以下几点:
APP来说,图片是造成内存问题的最大瓶颈点)
关于auto layout的布局,最直接的优化是使用手动布局计算frame。
因为自动布局的原理是:通过创建一个与view绑定的对象engine,使用engine记录下来相关的约束信息,在布局计算的时候,带入相关参数计算出来frame.
如果能去掉这一步,肯定是能节省性能的。
虽然iOS 12系统之后,苹果对auto layout进行了优化,优化后的效率和手动布局差不太多。但是我们的用户还是会有很多在12系统以下的。
因此,还是可以考虑优化的。
不过我目前还没有遇到过使用auto layout造成页面性能出现问题的案例。
首先我们项目里的本地图和加载过来的网图,都是经过压缩的二进制数据(常用的jpg,png)。
在显示到界面上的时候,需要将这些二进制数据绘制到对应的“画布”上。
这个绘制的过程就是解码。

系统会默认在UIImage加载到UIImageView或者CALayer的content上的时候,在主线程进行解码。
这个解码操作是耗时的,如果不处理可能会造成卡顿问题,因此需要放到子线程里去异步执行。
我们使用的三方框架都有此类处理。
比如SDWebImage是创建了一个串行队列,异步执行解码操作。
通过SDWebImageAvoidDecodeImage参数可以控制是否进行解码操作。
不过这里有个疑惑,为什么不使用并发队列?使用串行队列就意味着图片解码操作要顺序执行,这样效率岂不是有点低?
对于不同cell的滑动列表,可以利用缓存来避免多次计算,达到提效的目的。
可以做的简单点利用数据模型来持有这些信息。
也可以利用三方的框架: UITableView+FDTemplateLayoutCell
从上图可以看到,图片占用内存的大小计算方式是: width * height * 每个像素占用的内存大小(一般是4字节)
一张图,如果分辨率比较大,就容易造成很大的内存问题
当页面上有多个图片的时候,这个内存就会暴增。
SDWebImage在加载图片的时候默认会进行解码操作。
网上随便找了三张图,使用SDWebImageManager去加载。在开启和关闭解码操作后,在开启解码操作的时候,内存占用了10M。 关闭解码操作的时候,内存占用了5M。
使用第三方库的时候,如果只是预加载图片,可以考虑设置不解码。

DownSampling 就是在Decode的时候指定尺寸,只Decode部分数据,减少内存的使用。
比如我一个控件大小是100 * 100,但是原图可能是300 * 300的。使用DownSampling后,只需要解码少量数据就可以达到所需。
这个SDWebImage也已经支持,大家只需在加载图片的时候,利用context参数设置图片的大小和控件的大小相同即可。
@{SDWebImageContextImageThumbnailPixelSize:@(size)}
复制代码
经过我的实验,效果还是很明显的。
网上找了6张大图。
在不进行DownSampling的情况下,加载6张图片都消耗了 25M

但是在使用DownSampling的时候,指定尺寸为100*100,内存直接降低到了 5M

目前运用到项目中的首页Feeds流上,效果很显著。在只加载不到三屏的情况下(一屏大概4-6张图),都节省了40M左右。
注意使用的时候传入的尺寸要考虑到高清屏的系数。
因此,我们在搭建界面的时候,尽量加载和控件一样大的图,否则可能你看到的只是一个小图,其实占用了很大的内存,同时还需要CPU帮你去做一些压缩,剪裁的工作。
Image Assets去管理Image Assets的查找做了优化。
Backing Store的使用CALayer的contents所指向的区域。
如果使用了draw函数,CALayer会创建一个与view相同尺寸的Backing store,在上面进行draw的操作,然后提交到frame buffer中用于渲染。
这一步会造成内存的消耗。
因为系统的UILabel对单色的string做了优化处理,可节省75%的Backing Store,并能自动更新Backing Store的size以适配富文本或emoji。
对于一些复杂的view样式,可以通过多个subView组合到一起来实现,尽量减少draw的操作,就可以减少这部分内存的消耗。
对于纯色图片,尽量复用一个图片,用tint Color 来进行不同的渲染。达到复用图片的目的。
对于使用频繁的图片,可以使用[UIImage imageWithNamed:@""]方式创建,利用系统级别的缓存来提高效率,减少内存。
对于使用不频繁的图片,建议使用直接读文件的方式加载,用完就会自动释放,减少内存。
正常情况下,系统会按照60FPS或者120FPS的频率来执行渲染流程。
在每个屏幕渲染周期内,系统会从帧的缓冲区里拿到已经渲染好的数据,渲染到屏幕上。
而由于图层或者其他因素,导致在屏幕内无法直接渲染,需要在屏幕外开辟一个空间用来合成帧数据。
这就是所谓的离屏渲染。
离屏渲染之所以不好,原因是:
1.开辟了一块额外的空间,内存增加了
2.切换环境造成的牺牲很大
很容易发生在渲染周期内,数据无法渲染好,因此造成卡顿问题。
关于离屏渲染,实际开发中基本上都是:
对于设置阴影造成的离屏渲染,解决方式就是使用贝塞尔曲线绘制好path,这样就能解决问题。
对于UIImageView的圆角方案
最开始的时候,我的想法和网上的方案一样,就是:
利用子线程将图片进行切角处理,同时缓存下来这张图片,然后异步到主线程使用图片。这样就可以解决了圆角的离屏渲染问题。
但是在实际操作过程中,觉得这一步还是有问题的。
在滑动列表里,我们不可能一直不停地对图片进行切圆角操作。
不然就需要一直消耗CPU进行切圆角操作,还要频繁切换线程。
因此,我们就需要使用空间换时间了,将切好的圆角图片也缓存下来。
这个时候问题来了:一张图,被网络框架加载并存储了一份,现在又存下来了一张圆角图片
那基本上意味着内存占用double了一下。
虽然网上给出了很多的demo,但是在我看来写的都不好。
cell上的控件都是会重用的。如果不像SDWebImage一样先将之前的获取图片操作移除,如何确保重用的时候不出问题?
SDWebImage分类来的方便。(当然我们自己也可以使用一个分类来完成这个任务)
上面说的这些问题,其实都可以通过扩展SDWebImage来支持。
但是需要花费时间和精力来搞定,未来有机会的话,可以尝试一下。
对现在有的机器(iphone 11)进行了简单的验证,使用异步绘制圆角图片的方案解决离屏渲染后,通过instrument的分析,发现CPU和GPU都没有一个很明显的收益变化。
如果非得优化,让设计师切一个遮罩盖在上面是我认为的最好的解决方案。
毕竟前面也提到了,苹果认为组合subViews的方式比自己绘制的方式好很多。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。