iOS 调整图片 tips
最近写一个 Demo 时,需要用到一些本地的大图,发现在使用时能明显感觉到操作有一个卡卡的。解决这个小问题时,顺带将加载图片的流程复习了一下。
图片显示流程
原文来自于FastImageCache中,下面是我翻译的中文:
- UIImage(contentsOfFile: path) 使用 Image I/O 从内存映射数据创建 CGImageRef。在这时,图像尚未被解码。
- 将生成的 UIImage 赋值给 imageView.image。
- 隐式 CATransaction 捕获到 imageView 图层树的变化。
- 在主线程的下一个 Runloop 到来时,Core Animation 提交隐式事务,这可能会创建被设置图层内容的副本,根据设置的图片,复制操作可能涉及以下部分或全部步骤:
- 分配缓冲区来管理文件 IO 和解压缩操作。
- 文件数据从磁盘读取到内存中。
- 压缩的图像数据被解码成其未压缩的位图形式,这通常是非常耗费 CPU 的操作。
- Core Animation 使用未压缩的位图数据来渲染图层。
在上述过程中,解码操作时非常耗时,常见的三方库的操作都是异步解码,然后返回主线程使用。
图片解码内存占用问题
不管是网络图片还是本地图片,由于一些图片的尺寸比较大,在渲染时会导致内存瞬间飙涨。
对于网络图片,有些存储服务会提供在图片链接后面拼接参数来获取小图,这样就能避免内存飙涨的问题。但是,如果存储后台不提供这种服务的话,就需要开发自己解决这个问题了。
对于本地图片来说也是一样的。
所以,对于大图来说,我们需要怎么做才能以更好的方式来加载呢?
来自官方的 WWDC2018 的 Image and Graphics Best Practices 中,提供了一种利用 ImageIO 生成缩略图来进行降采样的方式。
这里,我将几种方式,放在一起记录一下。
降采样方式
- UIGraphicsImageRenderer
- Core Graphics Context
- Image I/O
- Core Image Lanczos 重采样
- vImage
下面几种方法中,图片原图尺寸 1200012000,渲染区域大小为屏幕上的 240240。
UIGraphicsImageRenderer
1 | func resizedImage() -> UIImage? { |
func image(actions: (UIGraphicsImageRendererContext) -> Void) -> UIImage 接受一个闭包参数并返回一个位图,该位图是执行传递的闭包的结果。在这种情况下,结果是按比例缩小以在指定范围内绘制的原始图像。
Core Graphics Context
1 | func resizedImage() -> UIImage? { |
Image I/O
1 | func resizedImage() -> UIImage? { |
Core Image Lanczos 重采样
1 | import CoreImage |
vImage
1 | import Accelerate.vImage |
这个方式,看起来更加的繁琐,一个 API 看起来也不是非常易用。总结这个流程还是很清晰的:
- 首先,从您的输入图像创建一个源缓冲区,
- 然后,创建一个目标缓冲区来保存缩放后的图像
- 接下来,将源缓冲区中的图像数据缩放到目标缓冲区,
- 最后,从目标缓冲区中的结果图像数据创建图像。
显示方式
1 | let url = Bundle.main.url(forResource: "VIIRS_3Feb2012_lrg", withExtension: "jpeg")! |
总结
UIImage(named: “”) 方式直接加兹安
- 十次加载平均耗时(s)
UIGraphicsImageRenderer | Core Graphics Context | Image I/O | Core Image Lanczos 重采样 | vImage |
---|---|---|---|---|
0.30405219793319704 | 0.3035615921020508 | 0.30096609592437745 | 1.2386974096298218 | 0.6203658103942871 |
- vImage 方式也会造成内存瞬间飙升,但马上会恢复正常。
- 肉眼看起来的话,UIGraphicsImageRenderer 和 Core Image 的方式最为清晰。
好了,这篇文章就到这里了。感兴趣的同学可以自己复制代码,换不同图片测试感受一下。