阅读 2131

iOS 页面优化

前言

关于页面的性能优化,我们能做些什么?个人想了一下,能想到的就是以下几点:

  • 优化布局计算

  • 异步进行耗时操作

  • 解决图片问题(对于APP来说,图片是造成内存问题的最大瓶颈点)

  • 避免离屏渲染

优化布局计算

关于auto layout的布局,最直接的优化是使用手动布局计算frame

因为自动布局的原理是:通过创建一个与view绑定的对象engine,使用engine记录下来相关的约束信息,在布局计算的时候,带入相关参数计算出来frame.

如果能去掉这一步,肯定是能节省性能的。

虽然iOS 12系统之后,苹果对auto layout进行了优化,优化后的效率和手动布局差不太多。但是我们的用户还是会有很多在12系统以下的。

因此,还是可以考虑优化的。

不过我目前还没有遇到过使用auto layout造成页面性能出现问题的案例。

异步耗时操作

图片解码操作

图片为什么需要解码?

首先我们项目里的本地图和加载过来的网图,都是经过压缩的二进制数据(常用的jpgpng)。

在显示到界面上的时候,需要将这些二进制数据绘制到对应的“画布”上。

这个绘制的过程就是解码。

image.png

异步图片解码

系统会默认在UIImage加载到UIImageView或者CALayercontent上的时候,在主线程进行解码。

这个解码操作是耗时的,如果不处理可能会造成卡顿问题,因此需要放到子线程里去异步执行。

我们使用的三方框架都有此类处理。

比如SDWebImage是创建了一个串行队列,异步执行解码操作。

通过SDWebImageAvoidDecodeImage参数可以控制是否进行解码操作。

不过这里有个疑惑,为什么不使用并发队列?使用串行队列就意味着图片解码操作要顺序执行,这样效率岂不是有点低?

缓存高度

对于不同cell的滑动列表,可以利用缓存来避免多次计算,达到提效的目的。

可以做的简单点利用数据模型来持有这些信息。

也可以利用三方的框架: UITableView+FDTemplateLayoutCell

解决图片问题

图片占有内存问题

从上图可以看到,图片占用内存的大小计算方式是: width * height * 每个像素占用的内存大小(一般是4字节)

一张图,如果分辨率比较大,就容易造成很大的内存问题

当页面上有多个图片的时候,这个内存就会暴增。

避免无谓的解码操作

SDWebImage在加载图片的时候默认会进行解码操作。

网上随便找了三张图,使用SDWebImageManager去加载。在开启和关闭解码操作后,在开启解码操作的时候,内存占用了10M。 关闭解码操作的时候,内存占用了5M

使用第三方库的时候,如果只是预加载图片,可以考虑设置不解码。

DownSampling(向下采样)

image.png

DownSampling 就是在Decode的时候指定尺寸,只Decode部分数据,减少内存的使用。

比如我一个控件大小是100 * 100,但是原图可能是300 * 300的。使用DownSampling后,只需要解码少量数据就可以达到所需。

这个SDWebImage也已经支持,大家只需在加载图片的时候,利用context参数设置图片的大小和控件的大小相同即可。

@{SDWebImageContextImageThumbnailPixelSize:@(size)}
复制代码

image.png

经过我的实验,效果还是很明显的。

网上找了6张大图。

在不进行DownSampling的情况下,加载6张图片都消耗了 25M

image.png

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

image.png

目前运用到项目中的首页Feeds流上,效果很显著。在只加载不到三屏的情况下(一屏大概4-6张图),都节省了40M左右。

注意使用的时候传入的尺寸要考虑到高清屏的系数。

因此,我们在搭建界面的时候,尽量加载和控件一样大的图,否则可能你看到的只是一个小图,其实占用了很大的内存,同时还需要CPU帮你去做一些压缩,剪裁的工作。

建议使用Image Assets去管理

  • 查找快

    如果是放到bundle里,是需要遍历bundle的文件夹。但是Image Assets的查找做了优化。

  • 更加智能的缓存策略

  • 可以减少拆分后的包体积

  • 支持图片拉伸等特性。

减少Backing Store的使用

什么是backing store?

CALayercontents所指向的区域。

如果使用了draw函数,CALayer会创建一个与view相同尺寸的Backing store,在上面进行draw的操作,然后提交到frame buffer中用于渲染。

这一步会造成内存的消耗。

因为系统的UILabel对单色的string做了优化处理,可节省75%的Backing Store,并能自动更新Backing Storesize以适配富文本emoji

对于一些复杂的view样式,可以通过多个subView组合到一起来实现,尽量减少draw的操作,就可以减少这部分内存的消耗。

图片复用

对于纯色图片,尽量复用一个图片,用tint Color 来进行不同的渲染。达到复用图片的目的。

对于使用频繁的图片,可以使用[UIImage imageWithNamed:@""]方式创建,利用系统级别的缓存来提高效率,减少内存。

对于使用不频繁的图片,建议使用直接读文件的方式加载,用完就会自动释放,减少内存。

离屏渲染

什么是离屏渲染?

正常情况下,系统会按照60FPS或者120FPS的频率来执行渲染流程。

在每个屏幕渲染周期内,系统会从帧的缓冲区里拿到已经渲染好的数据,渲染到屏幕上。

而由于图层或者其他因素,导致在屏幕内无法直接渲染,需要在屏幕外开辟一个空间用来合成帧数据。

这就是所谓的离屏渲染。

离屏渲染的坏处

离屏渲染之所以不好,原因是:

1.开辟了一块额外的空间,内存增加了

2.切换环境造成的牺牲很大

很容易发生在渲染周期内,数据无法渲染好,因此造成卡顿问题。

造成离屏渲染的方式

关于离屏渲染,实际开发中基本上都是:

  • 圆角+剪裁的组合

  • 设置layer的mask

  • 设置阴影

  • 光栅化

  • 抗锯齿

解决离屏渲染

对于设置阴影造成的离屏渲染,解决方式就是使用贝塞尔曲线绘制好path,这样就能解决问题。

这里想更多的介绍一下圆角方面的优化。

对于UIImageView的圆角方案

最开始的时候,我的想法和网上的方案一样,就是:

利用子线程将图片进行切角处理,同时缓存下来这张图片,然后异步到主线程使用图片。这样就可以解决了圆角的离屏渲染问题。

但是在实际操作过程中,觉得这一步还是有问题的。

问题一:内存占用增加

在滑动列表里,我们不可能一直不停地对图片进行切圆角操作。

不然就需要一直消耗CPU进行切圆角操作,还要频繁切换线程。

因此,我们就需要使用空间换时间了,将切好的圆角图片也缓存下来。

这个时候问题来了:一张图,被网络框架加载并存储了一份,现在又存下来了一张圆角图片

那基本上意味着内存占用double了一下。

问题二:实现功能很容易,但是想写好太难

虽然网上给出了很多的demo,但是在我看来写的都不好。

  • 首先,很粗暴的放到了一个全局并发队列里进行绘制,没有考虑到线程的消耗和安全问题。

  • 其次是没有一个很完善的防重用逻辑。

    一般我们都是使用的滑动视图,里面cell上的控件都是会重用的。如果不像SDWebImage一样先将之前的获取图片操作移除,如何确保重用的时候不出问题?

  • 使用起来麻烦

    如果自己实现了一套获取图片的逻辑,会发现代码量增加很多,远远不如使用SDWebImage分类来的方便。(当然我们自己也可以使用一个分类来完成这个任务)

上面说的这些问题,其实都可以通过扩展SDWebImage来支持。

但是需要花费时间和精力来搞定,未来有机会的话,可以尝试一下。

我认为的圆角最优解

对现在有的机器(iphone 11)进行了简单的验证,使用异步绘制圆角图片的方案解决离屏渲染后,通过instrument的分析,发现CPUGPU都没有一个很明显的收益变化。

如果非得优化,让设计师切一个遮罩盖在上面是我认为的最好的解决方案。

毕竟前面也提到了,苹果认为组合subViews的方式比自己绘制的方式好很多。

参考链接:

github.com/seedante/iO…

www.jianshu.com/p/a6bfaf1e0…

文章分类
iOS
文章标签