侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现线程安全的图片缓存系统

2025-12-12 / 0 评论 / 4 阅读

题目

实现线程安全的图片缓存系统

信息

  • 类型:问答
  • 难度:⭐⭐

考点

并发编程,内存管理,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等开源库的实现原理