预备知识
我们的对比主要关注内存的占用情况。对比的格式是 jpg 和 png 这两种最广泛使用的格式,分别代表了有损压缩和无损压缩;关于它们的特点和介绍,可以参考郭耀源的这篇文章:移动端图片格式调研。我们可以看到,在iOS 设备上它们的解码消耗在一个量级,速度较快。
WWDC2018 苹果重点关注了图片的内存占用情况(因为此前大家的通用做法实际上是相对低效的),并且给了大家一些指导。这里我们会用 demo 实验的方式(并且通过工具来进行 benchmark),为大家揭示一些事实和现象,供大家去理解和分析,从而去形成我们代码的最佳实践。
Demo: DownSampleDemo
场景
首先,苹果告诉我们,图片在应用中主要的内存占用(这通常发生在图片要被载入并显示时)和图片本身的大小实际是无关的;重要的是图片的尺寸。decode buffer 的计算方式是 width*height*N,这里的 N 通常是 4 (最常见的 ARGB888 格式),N取决于你显示所使用的格式。但很多时候,我们会直接把一个UIImage传递给UIImageView, 实际上该 View 的尺寸可能远远的小于 UIImage 本身。
使用图片的三种方式:
方式A
1 | image1.image = UIImage(named: "000.jpg") |
这是我们最通常使用图片的方式,可能有人会想到imagewithcontentsoffile,实际上它和上面的方法只有一个不同之处: 正常情况下 imageNamed 会缓存这个图片在整个App存续期间,这样就无需重复的 decode;而 imagewithcontentsoffile 则没有这个缓存,图片不使用了就会释放内存。但这对我们的 case 并无帮助,因为我们是在使用这些图片,并且观察它们的内存占用.
方式B
1 | + (UIImage *)OriginImage:(UIImage *)image scaleToSize:(CGSize)size { |
这是一种被广泛使用的缩放图片的办法。
方式C
1 | func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage { |
这是苹果介绍给我们的新方法。
关于测试
测试设备 : 1.iPhone8 11.4, 2.iPhone6 12beta2
测试图片格式 : png/jpg
测试的图片使用方式 :3种
我们将2560*1440大小的图片放到 282*138 的 View 中,对于缩放我们使用2x的格式来显示以保证其效果。
实验数据的细节在 DownSampleDemo 的 ViewController.swift 的注释中,这里直接告诉大家结论:
- Xcode 面板上所显示的内存占用并不可靠,在很多 case 下应用占用的内存要比其上显示的多的多(特别是对于iOS11.4的设备)。但可以用来粗略的查看内存占用,如果这里超了很多,那肯定是超了
- 可以使用 instruments 的 allocation 和 memory debugging 的 memgraph+ 命令行分析这两种方式来观测内存的占用
- 第一种方式下 jpg 载入后的内存分为 imageIO 和 IOkit 两部分,png 载入只有 imageIO 部分,jpg 内存占用比png要少不少
- 第二种方式的数据在 Xcode 面板上失真,通过另外两种方式可以看到它非常不靠谱。内存消耗甚至可能超过第一种。
- 第三种方式的内存占用非常完美,严格的遵守了公式计算出来的大小(跟 downsample 后的尺寸有关),内存占用在 CG raster data 中。
- memgraph 命令: vmmap –summary xxx.memgraph
- allocation 我们只需要看 VM allocation 栏目下的 dirty size 和 swapped size,这里需要手动的打开snapshot 才能看到该栏目的数据,并且需要适当的重复多次运行才能保证结果的正确性
VMTracker 运行截图:
memgraph 命令结果截图: