图片相关

iOS 中的图片

图片占用的内存

图片在内存中占用空间大小与文件大小无关,与其图像尺寸成正相关。一张尺寸为 2268 * 4032 的图片,其内存占用为:2268 * 4032 * 4 / 1024 / 1024 = 34.88M (这里假设每个像素占用的空间为 4 字节),而文件大小只有 3.7M

关于像素格式可以看这篇文章:谈谈 iOS 中图片的解压缩

图片的解码

iOS 中图片的解码过程处于屏幕显示过程中一次 Commit Transaction 中的 prepare 阶段,是处于主线程中的。图片的加载与渲染涉及到三个 buffer:

  • data buffer:存储图片的文件数据
  • image buffer:存储图片的解码后的像素数据
  • frame buffer:存储屏幕渲染后的数据

具体可以看这两篇文章:

主线程解码的问题

图片解码是 CPU 进行的,因此在一个性能敏感的界面中,比如列表中。如果因 CPU 解码导致未能在 1/60 秒内完成一帧的渲染, 就会造成丢帧,页面滑动卡顿。因此为避免在主线程解码,通常会在子线程中提前将图片解码,常见的第三方图片库用都是这么做的,比如 sd、YYImage 等。

子线程直接解码的问题

在子线程将整个图片进行解码会带来一个新的问题,那就是内存问题。通常图片尺寸是比在屏幕上的显示区域大很多的,在屏幕渲染时会对图片进行伸缩裁剪等调整。因此,可以在图片界面之前对图片进行下采样,减小 image buffer 占用。


UIImage 缓存是怎么回事

通过 imageNamed:创建 UIImage 时,系统只是在 Bundle 内查找到文件名,然后吧文件名放到 UIImage 中返回,并没有进行文件读取和解码。当 UIImage 第一次显示到屏幕上时,内部的解码方法才会被调用,同时解码结果会保存到全局的缓存中,在 App 第一次进入后台和收到内存警告时,缓存会被清除。

用 imageWithData 能不能避免缓存

不能。通过 data 创建图片时,UIImage 底层调用的时 ImageIO 的 CGImageSourceCreateWithData() 方法,该方法有个参数叫 shouldCache,在 64 位设备上,这个参数是默认开启的。图片同样是在第一次显示到屏幕上时才被解码的。随后解码数据被缓存到 CGImage 内部,与 imageNamed: 创建的图片不同,如果这个图片释放,其内部的解码数据也会被立刻释放。

怎么能避免缓存

  1. 手动调用 CGImageSourceCreateWithData() 创建图片,并把 shouldCache 和 CacheImmediately 设为 false。但这么做会导致图片每次显示到屏幕上时,解码方法都会被调用,造成很大的 CPU 占用。
  2. 把图片用 CGContextDrawImage() 画到画布上,然后把画布的数据取出来当做图片。这也是常见图片库的做法。

不用画布直获取图片解码后的数据

  1. CGImageSourceCreateWithData(data) 创建 ImageSource。
  2. CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。
  3. CGImageGetDataProvider(image) 获取这个图片的数据源。
  4. CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。
    ImageIO 解码发生在最后一步,这样获得的数据是没有经过颜色类型转换的原生数据(比如灰度图像)

kCGImageSourceShouldCache 与 kCGImageSourceShouldCacheImmediately 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Specifies whether the image should be cached in a decoded form. The
* value of this key must be a CFBooleanRef.
* kCFBooleanFalse indicates no caching, kCFBooleanTrue indicates caching.
* For 64-bit architectures, the default is kCFBooleanTrue, for 32-bit the default is kCFBooleanFalse.
*/

IMAGEIO_EXTERN const CFStringRef kCGImageSourceShouldCache;

/* Specifies whether image decoding and caching should happen at image creation time.
* The value of this key must be a CFBooleanRef. The default value is kCFBooleanFalse (image decoding will
* happen at rendering time).
*/
IMAGEIO_EXTERN const CFStringRef kCGImageSourceShouldCacheImmediately;

其中kCGImageSourceShouldCacheImmediately这个枚举默认是false,会导致图片的解码和缓存都是在图片第一次被渲染的时候才执行的。 kCGImageSourceShouldCache这个枚举在64位架构(截止到目前市面上的主流iPhone机型)的机器上默认是true。导致其实生成UIImage的时候,解码完成的时候都会缓存。