iOS image 内存优化
iOS Image 内存优化
结论
在iOS 原生层,对于图片的使用,推荐使用 UIGraphicsImageRenderer API
也需要合理在不同的使用场景,根据场景需要类型使用正确的API ,同时对图片资源做加载和释放管理。
前因
看看上面的这个图,有没有考虑过,iOS 里面一张 4096 * 3072
尺寸的png
图片占用多大内存呢 ?答案是惊人的 48M
计算公式是这样的 image width * image height * 4 /1024 / 1024
这里认为 image 属于RGBA8888
问题来了,内存都在哪里 ?
深究一下iOS 内存分配逻辑可以有下面的结论,iOS 内存部分分为三类,即:Data Buffer、Image Buffer、Frame Buffer
Data Buffer
是存储在内存中的原始数据,图像可以使用不同的格式保存,如 jpg、png。是Image 的文件内容。
Image Buffer
是图像在内存中的存在方式,用于存放图像具体素点信息。Image Buffer
的大小和图像的大小成正比。
Frame Buffer
和 Image Buffer
内容相同,不过其存储在 vRAM(video RAM)中,而 Image Buffer 存储在 RAM 中。
解码就是从 Data Buffer
生成 Image Buffer
的过程。Image Buffer
会占用带宽上传到 GPU
成为 Frame Buffer
,最后GPU负责使用 Frame Buffer
用于更新显示区域。
大致执行流程如上图,先经过载入,加载图像内容到内存成为Data Buffer , 然后就是经过Decode 过程,转化图像为GPU 可用的 Image Buffer ,在需要显示的时候Image Buffer data 会被上传到GPU 中成为Frame Buffer Data 进行相应渲染。
上图飙升的 48M 内存代码如下
1 | //原图加载 |
UIImage
是 iOS 中处理图像的高级类。创建一个 UIImage 实例只会加载 Data Buffer,将图像显示到屏幕上才会触发解码,也就是 Data Buffer 解码为 Image Buffer。Image Buffer 也关联在 UIImage 上。
imageNamed
这个常用API 存在一个内存问题,就是载入以后图片会被缓存到系统Cache 里面。 这是一种便捷的设计,比如可以快速在cache 里面查找到图片的缓存,同时也是一个弊端,内存就放在了cache 里面,一部分内存就会被持续占用。如果是比较小,又常用的图片,这么处理比较合适,但是针对于例子中的尺寸来讲,就非常不合适。
对于这个问题,大家通用的解决方案应该是 使用 imageWithContentsOfFile这个 API 来搞,根据苹果的解释是:使用这个方法创建的图片不会缓存于系统缓存内,开发者可在适当的时机对图片进行处理。因而,对于一些比较大的或不常使用的图片,我们应当使用imageWithContentsOfFile:
进行创建。
1 | -(void)withContexOfFile{ |
API 的本身用途是不在cache 里面缓存图片内容,但是图片占用的内存依然很大。
优点 : 图片不使用即释放内存,不存在图片常驻内存
缺点 : 每次使用都需要做IO的操作
适用于使用不频繁的大图加载
有没有更好的方式来降低图片内存 ?
答案,有! 参考 Image and Graphics Best Practices WWDC2018
所以引出这里的重点: UIGraphicsImageRenderer
代码先:
1 | -(void)resizeTest |
先给出一组数据对比 图片尺寸同样缩放在 1367 / 2 , 1089 / 2
图1 使用 imageNamed API , 发现 Physical footprint 明显增加了 48M, 内存块儿在 IOSurface
IOSurface 内存块儿增加是因为 图片decode 之后生成的位图,内存会被分类到这里
图2 使用 UIGraphicsImageRenderer API
明显观察到的内容是 占用巨大的 IOSurface 不存在了,但是内存块儿多了一个 CG raster data 大小为 5824K
1367 * 1089 * 4 / 1024 = 5815.08984375
跟 5824 很接近是不是 ? 可是又会有一点儿感觉不对,图片的宽和高都没有按照缩放的size 进行计算。于是就这个问题又去查了一下。
UIGraphicsImageRenderer 实现的原理是,系统可以根据图片分辨率选择创建解码图片的格式,如选用SRGB format 格式,每个像素占用 4 字节,而Alpha 8 format,每像素只占用 1 字节,因此可以减少大量的解码内存占用。
使用 UIGraphicsImageRenderer 之后这张图片physical footprint增加了多少内存 ? 0.2M
那么优化了多少内存呢 (48 - 0.2) / 48 = 0.996 , 释放了 96% 的可用空间。