iOS 图片解码、缓存与列表复用
iOS 图片解码、缓存与列表复用
图片文件在磁盘上的体积与显示时的内存占用并不相同。高分辨率图片完整解码后会占用大量内存,列表滚动时若同步解码或旧请求回写新单元格,还会产生卡顿和串图。图片管线需要统一处理目标尺寸、缓存、取消和主线程更新。
一、按显示尺寸解码
缩略图只需要接近控件像素尺寸的数据。直接创建原始图片对象,再交给视图缩小,无法避免完整解码成本。
可以让图片数据提供者根据目标尺寸生成缩略图:
struct ImageRequest: Hashable {
let key: String
let targetPixelSize: CGSize
let scaleMode: ScaleMode
}
protocol ImageDataProviding {
func loadData(for key: String) async throws -> Data
}
具体采样实现使用项目已有图片组件,并遵守最低系统版本。业务页面只传递稳定资源键和目标尺寸。
二、区分点与像素
视图布局尺寸通常以点表示,解码目标要结合屏幕缩放因子换算像素:
func pixelSize(for viewSize: CGSize, scale: CGFloat) -> CGSize {
CGSize(
width: viewSize.width * scale,
height: viewSize.height * scale
)
}
使用远大于目标的尺寸会浪费内存,过小则导致模糊。列表布局变化后,缓存键也要反映真正影响结果的尺寸档位。
三、使用有容量边界的内存缓存
NSCache 可以在系统内存压力下自动清理对象,但仍需要合理成本:
final class ImageMemoryCache {
private let cache = NSCache<NSString, UIImage>()
init(totalCostLimit: Int) {
cache.totalCostLimit = totalCostLimit
}
func image(for key: String) -> UIImage? {
cache.object(forKey: key as NSString)
}
func insert(_ image: UIImage, for key: String, cost: Int) {
cache.setObject(image, forKey: key as NSString, cost: cost)
}
}
成本可以按解码像素估算。缓存不是数据权威来源,被清除后必须能重新加载。
四、合并相同在途请求
同一图片同时出现在多个位置时,可以共享加载任务。最后一个使用者取消后,再决定是否取消底层任务。
在途键应包含资源版本、尺寸和变换。只按原始标识合并,可能把头像尺寸结果错误用于大图。
五、解决单元格串图
单元格需要持有当前图片任务,并在复用时取消:
final class CoverCell: UICollectionViewCell {
private var imageTask: Task<Void, Never>?
private var representedKey: String?
override func prepareForReuse() {
super.prepareForReuse()
imageTask?.cancel()
imageTask = nil
representedKey = nil
coverView.image = placeholderImage
}
func configure(with model: CoverModel) {
representedKey = model.imageKey
imageTask = Task { [weak self] in
guard let image = await imageLoader.image(for: model.imageKey) else {
return
}
guard !Task.isCancelled else { return }
await MainActor.run {
guard self?.representedKey == model.imageKey else { return }
self?.coverView.image = image
}
}
}
}
取消减少无用工作,键比较防止已经完成的旧结果覆盖新内容。
六、把解码与变换移出主线程
数据读取、图片解码、圆角裁剪和模糊处理不应阻塞主线程。最终设置视图必须回到主执行域。
系统图形对象并非所有操作都适合任意线程,具体实现应使用项目验证过的图片管线,不在业务层随意并发调用。
七、磁盘缓存要有失效策略
磁盘缓存减少重复读取,但需要:
- 容量和淘汰规则。
- 资源版本参与缓存键。
- 账户隔离和退出清理。
- 敏感图片是否允许落盘。
- 写入失败时不影响主业务流程。
用户头像更新后,应使用新版本键或明确删除旧缓存,不能只等待自然过期。
八、预取需要可取消
列表预取可以提前准备即将显示的图片,但快速滚动会产生大量无用任务。预取接口应支持取消,并与正常加载共享缓存和在途请求。
纯文本列表或很短列表没有必要启用复杂预取。
九、处理内存警告
收到内存压力信号后,图片缓存应按项目策略清理。当前屏幕正在显示的图片由视图持有,不会因为缓存清理立即消失。
不要在每次进入后台都无条件删除所有磁盘缓存,频繁重建可能增加耗电和通信成本。
十、测量真实场景
测试长列表快速滚动、图片详情反复进入、横竖屏切换和低内存场景。关注主线程解码、峰值内存、缓存命中和单元格串图。
瞬时峰值可能来自并发解码,持续增长则需要检查任务、缓存键和视图生命周期。
总结
iOS 图片性能的关键是按目标像素尺寸解码,用有边界的统一缓存复用结果,并让列表任务支持取消和身份校验。后台完成读取与变换,主线程只更新视图,再通过真实滚动测量内存峰值,才能兼顾清晰度与流畅度。
- 点赞
- 收藏
- 关注作者
评论(0)