性能
图片的内存按像素算
文件只有几百 KB,解码后仍可能是几十 MB。要降的是解码尺寸,不是再压一遍 JPEG。
一张 4032×3024 的照片,文件可能 2MB。解码成 bitmap 大约是宽 × 高 × 4 字节,也就是 48MB 上下。界面上只显示 120 点的头像,却把整张图交给 UIImageView,内存和主线程解码都会爆。列表里再来二十张,jetsam 的原因就很清楚了。
按目标尺寸解码
用 ImageIO 在解码时就限制最大边,而不是先 UIImage(contentsOfFile:) 再画到小上下文里。后一种仍然会先把大图展开。
import ImageIO
import UIKit
func thumbnail(at url: URL, maxPixel: Int) -> UIImage? {
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
return nil
}
let options = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxPixel
] as CFDictionary
guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else {
return nil
}
return UIImage(cgImage: image)
}
maxPixel 用点尺寸乘以屏幕 scale。120pt 的格子在 3x 上是 360 像素,没有必要解到 1000。方向要用 CreateThumbnailWithTransform,否则手机拍的竖图会歪。
缓存存什么
- 网络层缓存原文件或压缩数据,占用小,适合磁盘。
- 内存缓存存已经按屏幕尺寸解码的图。同一张图在不同尺寸出现,要分成不同的缓存键。
- UIImage 的 imageNamed 会按名字缓存,适合包内小图标,不适合用户相册和网络图。
滚动时取消离开屏幕的解码任务。prepareForReuse 里把 image 置空,并取消对应的 Task。不然复用到下一行时,上一行的大图还会写回来,既闪又占内存。
Asset Catalog 里的矢量图和 PDF 要看保留矢量还是栅格化。大面积拉伸的矢量在主线程上重新渲染,列表里一样会卡。图标小、张数少,矢量合适;复杂插画,导出固定倍率的位图更稳。