如何为SwiftUI中的AsyncImage添加图片缓存功能
AsyncImage 缓存解决方案
问题根因
原生AsyncImage默认使用系统共享URLSession,仅遵循HTTP协议默认缓存规则,若服务端未配置合理的缓存头,列表滑动时Cell复用会触发重复下载。你之前的缓存方案失效核心原因是缓存实例没有做成全局单例,放在值类型struct中会随实例重建被重置,无法跨页面/Cell复用缓存内容。
方案1:轻量改造(无自定义组件)
无需修改原有AsyncImage的业务逻辑,仅需两步配置即可生效:
- 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
- 将原来传
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组件:
- 先实现全局单例的缓存管理类:
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) } }
- 实现自定义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() } }
- 业务侧使用示例:
// 替换原有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
相关产品推荐
相关产品推荐

