You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为SwiftUI中的AsyncImage添加图片缓存功能

AsyncImage 缓存解决方案

问题根因

原生AsyncImage默认使用系统共享URLSession,仅遵循HTTP协议默认缓存规则,若服务端未配置合理的缓存头,列表滑动时Cell复用会触发重复下载。你之前的缓存方案失效核心原因是缓存实例没有做成全局单例,放在值类型struct中会随实例重建被重置,无法跨页面/Cell复用缓存内容。


方案1:轻量改造(无自定义组件)

无需修改原有AsyncImage的业务逻辑,仅需两步配置即可生效:

  1. App启动时全局配置URLCache大小,放在App入口或者didFinishLaunchingWithOptions中:
// 配置100MB内存缓存 + 500MB磁盘缓存
let cacheSizeMemory = 100 * 1024 * 1024
let cacheSizeDisk = 500 * 1024 * 1024
let sharedCache = URLCache(memoryCapacity: cacheSizeMemory, diskCapacity: cacheSizeDisk, diskPath: "commonImageCache")
URLCache.shared = sharedCache
  1. 将原来传URL的AsyncImage改为传自定义缓存策略的URLRequest:
AsyncImage(urlRequest: URLRequest(
    url: URL(string: urlString)!,
    cachePolicy: .returnCacheDataElseLoad, // 优先读缓存,无缓存再请求
    timeoutInterval: 15
)) { phase in
    // 原有phase处理逻辑完全不变
    switch phase {
    case .success(let image):
        image
            .someModifers
    case .empty:
        Image(systemName: "photo")
            .someModifers
    case .failure(_):
        Image(systemName: "exclamationmark.triangle")
            .someModifers
    @unknown default:
        Image(systemName: "photo")
            .someModifers
    }
}

方案2:自定义可控缓存(支持自定义缓存规则)

如果需要无视服务端缓存头、自定义缓存过期时间、手动清理缓存等能力,可以自定义带缓存的AsyncImage组件:

  1. 先实现全局单例的缓存管理类:
class ImageCacheManager {
    static let shared = ImageCacheManager()
    // 内存缓存
    private let memoryCache = NSCache<NSString, UIImage>()
    
    private init() {
        // 配置内存缓存上限:最多100张图,最大100MB
        memoryCache.countLimit = 100
        memoryCache.totalCostLimit = 100 * 1024 * 1024
    }
    
    func getCacheImage(url: String) -> UIImage? {
        memoryCache.object(forKey: url as NSString)
    }
    
    func saveCacheImage(url: String, image: UIImage) {
        memoryCache.setObject(image, forKey: url as NSString)
    }
}
  1. 实现自定义CachedAsyncImage组件,用法和原生AsyncImage完全一致:
struct CachedAsyncImage<Content: View>: View {
    private let urlStr: String
    private let contentBuilder: (AsyncImagePhase) -> Content
    @State private var currentPhase: AsyncImagePhase = .empty
    
    init(url: String, @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) {
        self.urlStr = url
        self.contentBuilder = content
    }
    
    var body: some View {
        contentBuilder(currentPhase)
            .onAppear {
                loadImage()
            }
    }
    
    private func loadImage() {
        // 优先读内存缓存
        if let cacheImg = ImageCacheManager.shared.getCacheImage(url: urlStr) {
            currentPhase = .success(Image(uiImage: cacheImg))
            return
        }
        guard let targetUrl = URL(string: urlStr) else {
            currentPhase = .failure(NSError(domain: "ImageCache", code: -1))
            return
        }
        // 读磁盘缓存/发起网络请求
        let req = URLRequest(url: targetUrl, cachePolicy: .returnCacheDataElseLoad)
        URLSession.shared.dataTask(with: req) { data, _, error in
            DispatchQueue.main.async {
                if let error = error {
                    currentPhase = .failure(error)
                    return
                }
                guard let imgData = data, let uiImg = UIImage(data: imgData) else {
                    currentPhase = .failure(NSError(domain: "ImageCache", code: -2))
                    return
                }
                // 存入内存缓存
                ImageCacheManager.shared.saveCacheImage(url: urlStr, image: uiImg)
                currentPhase = .success(Image(uiImage: uiImg))
            }
        }.resume()
    }
}
  1. 业务侧使用示例:
// 替换原有AsyncImage即可,内部逻辑完全复用
CachedAsyncImage(url: urlString) { phase in
    switch phase {
    case .success(let image):
        image
            .resizable()
            .scaledToFit()
    case .empty:
        ProgressView()
    case .failure(_):
        Image(systemName: "exclamationmark.triangle")
    @unknown default:
        Image(systemName: "photo")
    }
}

内容的提问来源于stack exchange,提问作者Luis Ramirez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 00:15:04