侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

设计高性能图片缓存组件并解决线程安全与内存抖动问题

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

题目

设计高性能图片缓存组件并解决线程安全与内存抖动问题

信息

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

考点

多级缓存设计,线程安全实现,内存优化,缓存淘汰策略,网络请求优化

快速回答

高性能图片缓存组件的核心设计要点:

  • 三级缓存架构:内存缓存(NSCache)+ 磁盘缓存(FileManager)+ 网络下载
  • 线程安全:使用串行队列+屏障保证读写安全
  • 内存优化:解码后图片存储、自动清理机制、Downsampling技术
  • 缓存策略:LRU淘汰算法+Cost-based清理
  • 网络优化:请求合并、任务取消、渐进式加载
## 解析

1. 核心架构设计

三级缓存工作流

func loadImage(url: URL) -> UIImage? {
    // 1. 检查内存缓存
    if let memCached = memoryCache.object(forKey: url.absoluteString) {
        return memCached
    }

    // 2. 检查磁盘缓存
    if let diskCached = diskCache.getImage(for: url) {
        // 异步解码后存入内存
        DispatchQueue.global().async {
            let decoded = self.decodeImage(diskCached)
            self.memoryCache.setObject(decoded, forKey: url.absoluteString)
        }
        return diskCached
    }

    // 3. 网络下载
    downloadTask = URLSession.shared.dataTask(with: url) { data, _, _ in
        guard let data = data else { return }
        // 后台线程处理
        DispatchQueue.global().async {
            // 下采样大图
            let downsampled = self.downsampleImage(data, to: imageViewSize)
            // 磁盘存储
            self.diskCache.storeImage(downsampled, for: url)
            // 内存缓存
            self.memoryCache.setObject(downsampled, forKey: url.absoluteString)
        }
    }
    downloadTask?.resume()
}

2. 线程安全实现

使用GCD屏障保证读写安全

private let syncQueue = DispatchQueue(label: "com.cache.sync", attributes: .concurrent)

func safeSetImage(_ image: UIImage, for key: String) {
    syncQueue.async(flags: .barrier) {
        self.memoryCache[key] = image
        // 更新LRU链表
        self.accessRecorder.moveToHead(for: key)
    }
}

3. 内存优化关键技术

  • Downsampling:处理大图时使用ImageIO进行下采样
    func downsampleImage(data: Data, to size: CGSize) -> UIImage {
        let options = [kCGImageSourceShouldCacheImmediately: false]
        guard let source = CGImageSourceCreateWithData(data as CFData, options as CFDictionary) else { return }
    
        let maxDimension = max(size.width, size.height) * UIScreen.main.scale
        let downsampleOptions = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceShouldCacheImmediately: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceThumbnailMaxPixelSize: maxDimension
        ] as CFDictionary
    
        guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) 
        else { return }
    
        return UIImage(cgImage: cgImage)
    }
  • 解码后存储:避免主线程解码卡顿
  • Cost-based清理:根据图片像素大小计算cost

4. 缓存淘汰策略

LRU实现方案

class LRUCache {
    private class CacheNode {
        var key: String
        var value: UIImage
        var prev: CacheNode?
        var next: CacheNode?
    }

    private var head: CacheNode?
    private var tail: CacheNode?
    private var cacheMap = [String: CacheNode]()

    func moveToHead(for key: String) {
        guard let node = cacheMap[key] else { return }
        // ... 链表节点移动操作
    }

    func removeLast() {
        guard let tailNode = tail else { return }
        cacheMap.removeValue(forKey: tailNode.key)
        // ... 更新链表
    }
}

5. 网络请求优化

  • 请求合并:相同URL只创建一个任务
  • 任务取消:cell复用时的请求取消
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell) {
        if let task = cell.currentImageTask {
            task.cancel()
        }
    }
  • 渐进式加载:支持JPEG渐进式显示

6. 常见错误与解决方案

错误类型后果解决方案
主线程解码界面卡顿强制在后台线程解码
未计算内存costOOM崩溃根据像素大小计算cost
线程竞争随机崩溃屏障队列保护共享资源
未处理复用图片错乱绑定请求与cell实例

7. 扩展知识

  • 响应式内存警告:监听UIApplication.didReceiveMemoryWarningNotification清空缓存
  • 磁盘缓存加密:使用SQLCipher加密敏感图片
  • WebP支持:集成libwebp解码器
  • Prefetching:结合UITableViewDataSourcePrefetching预加载