import SwiftUI struct CachedAsyncImage: View { let url: URL? @ViewBuilder let placeholder: () -> Placeholder @State private var image: Image? @State private var isLoading = false var body: some View { Group { if let image { image .resizable() .aspectRatio(contentMode: .fill) } else { placeholder() .task(id: url) { await loadImage() } } } } private func loadImage() async { guard let url, !isLoading else { return } // 检查内存缓存 if let cached = ImageCache.shared.get(url) { self.image = cached return } isLoading = true defer { isLoading = false } do { let (data, _) = try await ImageCache.shared.session.data(from: url) #if os(macOS) if let nsImage = NSImage(data: data) { let img = Image(nsImage: nsImage) ImageCache.shared.set(img, for: url) self.image = img } #else if let uiImage = UIImage(data: data) { let img = Image(uiImage: uiImage) ImageCache.shared.set(img, for: url) self.image = img } #endif } catch { // 加载失败保持 placeholder } } } // MARK: - 图片内存缓存 private final class ImageCache: @unchecked Sendable { static let shared = ImageCache() private let cache = NSCache() let session: URLSession private init() { cache.countLimit = 200 cache.totalCostLimit = 100 * 1024 * 1024 // 100MB // 配置磁盘缓存 let config = URLSessionConfiguration.default config.urlCache = URLCache( memoryCapacity: 50 * 1024 * 1024, // 50MB 内存 diskCapacity: 200 * 1024 * 1024 // 200MB 磁盘 ) config.requestCachePolicy = .returnCacheDataElseLoad session = URLSession(configuration: config) } func get(_ url: URL) -> Image? { cache.object(forKey: url as NSURL)?.image } func set(_ image: Image, for url: URL) { cache.setObject(CacheEntry(image: image), forKey: url as NSURL) } } private final class CacheEntry { let image: Image init(image: Image) { self.image = image } }