高清大图加载的问题
之前在项目中遇到了加载高清大图的场景,处理不好占用大量内存,造成性能问题,影响用户体验。
如何去解决加载高清大图这个难点呢?先看看iOS中的图片加载流程。
iOS中图片的加载流程
国外有文章对图片的工作流写的很清楚,这里是链接。
简单来说是三步:
- 1、加载图片数据,这里的图片数据尚未解码
- 2、解码图片数据
- 3、渲染图片
性能问题的一般是发生在解码图片这个步骤上,SDWebImage是提前强解码图片,这样在渲染时就不会再发生解码,其强解码的代码是在SDWebImageCoderHelper
中的decodedImageWithImage
函数中CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
。
用SDWebImage能解决吗?
先说结论:不能。
测试SDWebImage的代码如下:
imageView.sd_setImage(with: url!) { (image, error, cacheType, theUrl) in
print(image?.size)
}
使用Instruments可以看出SDWebImage的内存占用非常大,加载测试图
这样图片内存占用达到270MB,峰值内存达到800MB+。
可以看出,SDWebImage无法解决高清图内存占用过高和内存峰值过高的问题。
优化的思路
SDWebImage加载高清图的性能问题,主要是其直接加载高清图的原来尺寸导致的,而加载一张图片的内存占用 = 图片Width * 图片Height * 4
。所以直接加载测试图
的原始分辨率的内存占用是 7033 * 10100 * 4 / (1024 * 1024) = 约270MB
。
因此,对于高清图的加载,需要采用缩减分辨率的方法来减轻加载的内存压力,这里使用DownSampling
的方法,具体的方法介绍看WWDC2018-最佳图像实践这个Session。
DownSampling的具体原理是,在图像解码之前加入创建缩略图的过程,对加载的Image进行预处理,减少解码后的Image Buffer的大小,从而减少加载的内存占用和内存峰值。
这是DownSampling的示例代码:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
//生成CGImageSourceRef 时,不需要先解码。
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
//在创建Thumbnail时直接解码
let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
//生成UIImage,强制解码
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}
这是使用DownSampling优化后的Instrument内存监测:
对比
加载方式 | 内存占用 | 内存峰值 |
---|---|---|
SDWebImage | 270MB | 800MB+ |
DownSampling | 14MB | 40MB |
从对比中可以看出,使用DownSampling能大大降低内存的占用和峰值。
总结
对于高清大图的场景来说,内存的占用尤为重要,如何降低图片占用的内存,对提升用户体验有着至关重要的作用。从上面的对比中可以看出,使用SDWebImage是难以应对高清大图这种场景的,而DownSampling这种缩略图的方案却能很好适应这种场景。