题目
实现线程安全的图片缓存系统
信息
- 类型:问答
- 难度:⭐⭐
考点
并发编程,内存管理,Swift协议应用
快速回答
实现线程安全的图片缓存系统需要:
- 使用
NSCache作为基础存储,自动处理内存警告 - 通过
DispatchQueue配合屏障(.barrier)实现读写安全 - 采用
URLSessionDataTask处理网络请求 - 实现
Cacheable协议保证扩展性 - 处理缓存命中/未命中场景及错误情况
原理说明
线程安全缓存需要解决两个核心问题:1) 多线程环境下的数据竞争 2) 内存资源的合理管理。Swift中应使用:
- NSCache:比Dictionary更适合缓存,自动释放内存,线程安全基础
- 屏障队列:并发队列+屏障标志实现高效读写隔离(读并发,写独占)
- 弱引用包装:避免因缓存导致的内存泄漏
代码实现
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
func set(_ value: Value, for key: Key)
func remove(for key: Key)
}
final class ImageCache: Cacheable {
typealias Key = URL
typealias Value = UIImage
private let cache = NSCache<NSURL, UIImage>()
private let queue = DispatchQueue(label: "com.imageCache.queue",
attributes: .concurrent)
init() {
cache.countLimit = 100 // 限制缓存数量
cache.totalCostLimit = 1024 * 1024 * 100 // 100MB内存限制
}
func get(_ key: URL) -> UIImage? {
queue.sync {
cache.object(forKey: key as NSURL)
}
}
func set(_ value: UIImage, for key: URL) {
queue.async(flags: .barrier) { [weak self] in
self?.cache.setObject(value, forKey: key as NSURL,
cost: self?.calculateCost(for: value) ?? 0)
}
}
private func calculateCost(for image: UIImage) -> Int {
return image.pngData()?.count ?? 0
}
}
// 使用示例
let cache = ImageCache()
// 获取图片
if let cachedImage = cache.get(imageURL) {
// 缓存命中
} else {
// 网络请求
URLSession.shared.dataTask(with: imageURL) { data, _, _ in
guard let data = data, let image = UIImage(data: data) else { return }
cache.set(image, for: imageURL)
}.resume()
}最佳实践
- 内存成本计算:根据图片实际内存占用设置cost,而非单纯数量限制
- 二级缓存策略:内存缓存+NSCache+磁盘缓存三级体系
- 请求去重:对相同URL的请求进行合并,避免重复下载
- 弱引用容器:使用
WeakRef包装对象避免循环引用
常见错误
- 线程安全问题:直接使用Dictionary而未加锁导致崩溃
- 内存泄漏:缓存持有ViewController导致无法释放
- 缓存雪崩:同时大量缓存失效导致请求暴增
- 成本计算不准确:使用
image.size而非实际字节数计算内存
扩展知识
- LRU淘汰策略:当缓存满时优先移除最久未使用的资源
- 内存警告响应:通过
NotificationCenter监听UIApplication.didReceiveMemoryWarningNotification - 磁盘缓存实现:使用
FileManager将图片存储到cachesDirectory - 第三方库对比:Kingfisher/SDWebImage等开源库的实现原理