结论
先说结论,不使用 Downsampling 与使用 Downsampling,图片在内存中的占用大小分别如下图所示
问题
图片显示有一个涉及内存的问题是:
如上面所显示的图片,在 Finder 中显示磁盘的占用大小为 3.4 MB,而在内存中其占用大小会是 14.1 MB
原因
造成上图的原因在于,图片在磁盘占用大小并不等于内存的占用大小
图片在内存中的占用大小至于图片的大小有关,而与图片文件(在磁盘上)的大小无关
在 iOS 上,要显示一张图片,系统首先会解码与解压缩图片数据
通常来说,解码后的图片,1 个像素会占用 4 bytes
1 byte for red, 1 byte for green, 1 byte for blue, and 1 byte for the alpha component
以上面显示的图片为例,其尺寸大小为 2560x1440
于是,这张图片在内存的占用大小为
(2560 * 1440) * 4bytes = 14,336,000bytes = 14.0625MB
解决
若想通过缩小和重绘图片来解决这个问题,实际上也不能解决问题
- 首先,调整一张图片的大小是比较消耗 CPU 的工作
- 其次,在调整图片大小时,系统依然需要对原始图片进行解码和解压缩,实际上内存占用仍然会有那么几个时刻暴增
最后,我们需要使用 ImageIO 实现 Downsampling,具体算法
func downsample( imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat = UIScreen.main.scale ) -> UIImage? { // Create an CGImageSource that represent an image let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else { return nil } // Calculate the desired dimension let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale // Perform downsampling let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels ] as CFDictionary guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil } // Return the downsampled image as UIImage return UIImage(cgImage: downsampledImage) }
选项解释
kCGImageSourceShouldCache
当这个选项被设置为
false
时, Core Graphic framework 在创建 CGImageSource
对象时,不会同时解码图片数据kCGImageSourceShouldCacheImmediately
当这个选项被设置为
true
时, Core Graphic 会将 downsampling 与解码图片数据两个操作同时进行结合
kCGImageSourceShouldCache
与 kCGImageSourceShouldCacheImmediately
两个选项,开发者就控制了使用 CPU 进行解码操作的时机kCGImageSourceCreateThumbnailWithTransform
当这个选项被设置为
true
时, Core Graphic 会保持 downsampled 图片与原始图片的朝向相同什么时候应该使用 Downsampling
需要注意的是, Downsampling 操作也是一个消耗 CPU 的操作,因此,应该尽可能在「图片源」处就将图片调整到合适大小
使用 Downsampling 的时机,便是图片的大小远大于显示图片的区域(即过于浪费的时候就应该用了)
对于需要经常使用图片,并且它是需要 Downsampling 的话,应该将 downsampled 的图片缓存起来
计算图片占用内存大小的方法
let cgImage = <anImage>.cgImage! let result = cgImage.bytesPerRow * cgImage.height