侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

设计一个图片下载和缓存机制

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

题目

设计一个图片下载和缓存机制

信息

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

考点

网络请求, 缓存策略, 多线程管理, 内存管理

快速回答

实现一个高效的图片下载缓存机制需要:

  • 使用NSCache实现内存缓存,响应内存警告
  • 采用NSOperationQueue管理下载任务,控制并发量
  • 磁盘缓存使用FileManager存储到Caches目录
  • 下载过程使用URLSessionDataTask支持渐进式加载
  • 处理重复请求和线程安全问题
## 解析

核心设计原理

高效图片缓存机制需分层实现:
1. 内存缓存:使用NSCache存储解码后的图片,比Dictionary更优(自动释放内存)
2. 磁盘缓存:将图片数据写入Caches目录,使用FileManager管理
3. 下载队列:通过OperationQueue控制并发,避免线程爆炸

代码实现示例

class ImageLoader {
    static let shared = ImageLoader()

    // 内存缓存
    private let memoryCache = NSCache<NSString, UIImage>()
    // 下载队列
    private let downloadQueue = OperationQueue()
    // 追踪进行中的任务
    private var runningRequests = [UUID: URLSessionDataTask]()

    init() {
        downloadQueue.maxConcurrentOperationCount = 5
        // 内存警告处理
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(clearMemoryCache),
            name: UIApplication.didReceiveMemoryWarningNotification,
            object: nil
        )
    }

    @objc func clearMemoryCache() {
        memoryCache.removeAllObjects()
    }

    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) -> UUID? {
        // 1. 检查内存缓存
        if let image = memoryCache.object(forKey: url.absoluteString as NSString) {
            completion(image)
            return nil
        }

        // 2. 检查磁盘缓存(伪代码)
        if let diskImage = loadFromDisk(url: url) {
            memoryCache.setObject(diskImage, forKey: url.absoluteString as NSString)
            completion(diskImage)
            return nil
        }

        // 3. 创建下载任务
        let uuid = UUID()
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            defer { self?.runningRequests.removeValue(forKey: uuid) }

            guard let data = data, error == nil,
                  let image = UIImage(data: data) else {
                completion(nil)
                return
            }

            // 缓存到内存
            self?.memoryCache.setObject(image, forKey: url.absoluteString as NSString)
            // 存储到磁盘(伪代码)
            self?.saveToDisk(image: data, url: url)

            DispatchQueue.main.async { completion(image) }
        }
        task.resume()

        // 记录进行中的任务
        runningRequests[uuid] = task
        return uuid
    }

    func cancelLoad(_ uuid: UUID) {
        runningRequests[uuid]?.cancel()
        runningRequests.removeValue(forKey: uuid)
    }
}

最佳实践

  • 内存优化:使用NSCache而非Dictionary,它会在内存紧张时自动释放
  • 磁盘存储
    • 存储到Caches目录(系统可自动清理)
    • 使用FileManager创建子目录分类存储
    • 对文件名做MD5哈希避免非法字符
  • 线程安全
    • 使用@synchronized或串行队列保护runningRequests
    • 所有缓存操作在后台线程执行

常见错误

  • 内存泄漏:未使用[weak self]导致循环引用
  • 线程阻塞:在主线程解码大图导致卡顿(应在后台线程解码)
  • 缓存穿透:相同URL重复发起下载,需用runningRequests去重
  • 磁盘误存:将缓存写入Documents目录导致无法被系统清理

扩展知识

  • 渐进式加载:使用URLSessionDataTaskdidReceiveData逐步显示图片
  • 缓存淘汰策略:LRU(最近最少使用)算法实现磁盘缓存清理
  • 第三方库对比
    • SDWebImage:成熟稳定,支持动图
    • Kingfisher:Swift编写,语法更现代
    • Nuke:高性能,低内存占用