题目
设计高性能图片缓存组件并处理大图内存优化
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
内存管理,多线程同步,缓存策略,图像处理,性能优化
快速回答
设计高性能图片缓存需实现:
- 三级缓存架构:内存缓存(NSCache)+ 磁盘缓存(FileManager)+ 网络下载
- LRU淘汰策略:通过双向链表实现最近最少使用淘汰
- 大图处理:使用ImageIO进行子采样(downsampling)
- 线程安全:GCD串行队列配合barrier保证读写安全
- 解码优化:后台线程解码并缓存解码后图像
1. 核心架构设计
三级缓存流程:
- 检查内存缓存(NSCache)
- 未命中则检查磁盘缓存(文件系统)
- 最后发起网络请求并缓存结果
class ImageCache {
private let memoryCache = NSCache<NSString, UIImage>()
private let diskCacheDir: URL
private let ioQueue = DispatchQueue(label: "com.cache.io", qos: .utility)
func image(for url: URL) -> UIImage? {
// 1. 检查内存缓存
if let image = memoryCache.object(forKey: url.absoluteString as NSString) {
return image
}
// 2. 检查磁盘缓存
var diskImage: UIImage?
ioQueue.sync {
let filePath = diskCachePath(for: url)
diskImage = UIImage(contentsOfFile: filePath.path)
}
// 3. 网络请求(伪代码)
if diskImage == nil {
downloadImage(url) { image in
self?.storeImage(image, for: url)
}
} else {
memoryCache.setObject(diskImage!, forKey: url.absoluteString as NSString)
}
return diskImage
}
}
2. 内存优化关键技术
大图子采样(Downsampling):
func downsample(imageAt sourceURL: URL, to maxSize: CGSize) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(sourceURL as CFURL, imageSourceOptions) else { return nil }
let options: [CFString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(maxSize.width, maxSize.height),
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true
]
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: downsampledImage)
}
优势:避免加载全尺寸Bitmap到内存,内存占用降低80%+
3. 缓存策略实现
LRU双向链表实现:
class LRUCache<Key: Hashable, Value> {
private class Node {
var key: Key
var value: Value
var prev: Node?
var next: Node?
}
private var head: Node?, tail: Node?
private var cacheDict = [Key: Node]()
private let lock = NSLock()
func setValue(_ value: Value, for key: Key) {
lock.lock()
defer { lock.unlock() }
if let node = cacheDict[key] {
node.value = value
moveToHead(node)
} else {
let newNode = Node(key: key, value: value)
addNode(newNode)
cacheDict[key] = newNode
}
}
// 淘汰逻辑(伪代码)
private func removeTail() {
guard let tailNode = tail else { return }
cacheDict.removeValue(forKey: tailNode.key)
// 移除尾节点并更新链表
}
}
4. 线程安全方案
- 读写锁模式:使用GCD barrier保证写操作独占
- 内存缓存:NSCache自带线程安全
- 磁盘操作:专用串行队列管理
private let concurrentQueue = DispatchQueue(
label: "com.cache.concurrent",
attributes: .concurrent
)
func safeSetImage(_ image: UIImage, for key: String) {
concurrentQueue.async(flags: .barrier) {
self.memoryCache.setObject(image, forKey: key as NSString)
self.saveToDisk(image, key: key) // 同步保存
}
}
5. 常见错误与优化
| 错误 | 后果 | 解决方案 |
|---|---|---|
| 主线程解码大图 | 界面卡顿 | 后台线程解码+子采样 |
| 未限制缓存大小 | 内存溢出崩溃 | 设置costLimit并监听内存警告 |
| 重复下载相同URL | 资源浪费 | 使用NSOperationQueue管理下载任务 |
| 磁盘缓存未压缩 | 空间浪费 | 存储JPEG格式(有损)或HEIC(iOS11+) |
6. 扩展知识
- 内存警告处理:监听
UIApplication.didReceiveMemoryWarningNotification清空NSCache - 渐进式加载:使用
CGImageSourceCreateIncremental实现模糊→清晰加载 - WebP支持:集成libwebp解码器获得更高压缩率
- 缓存验证:HTTP ETag/Last-Modified头避免重复下载