示例代码均为匿名化改写,用于说明模式;生产实现还应限制下载大小并根据显示尺寸下采样。
相同图片避免重复下载
一个页面上的多个卡片可能同时引用同一 URL。图片管线可以用 actor 按 URL 合并在途任务,再把成功解码的图像放入 NSCache。内存缓存适合加速滚动和会话内复用,不应被描述为离线持久存储。
actor ImageRequestPool {
private struct Entry {
let id: UUID
let task: Task<Data, Error>
}
private var inFlight: [URL: Entry] = [:]
func data(for url: URL, session: URLSession = .shared) async throws -> Data {
let entry: Entry
if let existing = inFlight[url] {
entry = existing
} else {
let id = UUID()
let task = Task {
let (data, response) = try await session.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
entry = Entry(id: id, task: task)
inFlight[url] = entry
}
do {
let result = try await entry.task.value
if inFlight[url]?.id == entry.id { inFlight[url] = nil }
return result
} catch {
if inFlight[url]?.id == entry.id { inFlight[url] = nil }
throw error
}
}
}
远程图片视图可以把加载结果转成占位、成功和失败状态,并提供无障碍描述。占位动画应遵守减少动态效果设置;失败图也要有明确、稳定的呈现。
共享下载任务不应由单个离屏 cell 的取消动作直接取消;可以用订阅计数管理取消,也可以让共享下载完成后写入缓存,再由各个视图独立决定是否继续等待。
按显示场景请求合适尺寸
列表缩略图、头像、横幅和全屏预览对分辨率及裁剪方式有不同要求。统一的 URL 适配层可以按场景生成处理参数,减少客户端下载远超显示尺寸的原图。
若源地址含有签名查询参数,应避免随意改写 URL,否则服务端签名可能失效。对不受支持的主机则保留原地址。
仍需关注单张大图
内存缓存的成本上限是在图像解码之后统计,不能阻止单张大图在解码瞬间占用大量内存。若图片尺寸不受控,可以在解码前限制像素尺寸并使用下采样,同时限制下载体积、校验响应类型并检查页面取消后的任务生命周期。
NSCache.countLimit 限制的是条目数,不是实际内存。应按解码成本设置预算,并在解码前按显示尺寸下采样,避免少量大图占满内存。
