如何为 AsyncImage 添加缓存

Lui*_*rez 49 swift swiftui

我是 SwiftUI 新手,正在研究如何从 URL 下载图像。我发现在 iOS15 中你可以使用 AsyncImage 来处理图像的所有阶段。代码如下所示。

    AsyncImage(url: URL(string: urlString)) { phase in
        switch phase {
        case .success(let image):
            image
                .someModifers
        case .empty:
            Image(systemName: "Placeholder Image")
                .someModifers
        case .failure(_):
            Image(systemName: "Error Image")
                .someModifers
        @unknown default:
            Image(systemName: "Placeholder Image")
                .someModifers
        }
    }
Run Code Online (Sandbox Code Playgroud)

我会启动我的应用程序,每次我在列表上上下滚动时,它都会再次下载图像。那么我怎样才能添加缓存呢?我试图像在 Swift 中那样添加缓存。像这样的东西。

struct DummyStruct {
  var imageCache = NSCache<NSString, UIImage>()
  func downloadImageFromURLString(_ urlString: String) {
    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let _ = error {
            fatalError()
        }
        
        guard let data = data, let image = UIImage(data: data) else { return }
        imageCache.setObject(image, forKey: NSString(string: urlString))
    }
    .resume()
  }
}
Run Code Online (Sandbox Code Playgroud)

但事情并没有那么顺利。所以我想知道有没有办法为 AsyncImage 添加缓存?感谢您的帮助。

Lor*_*ngo 66

我和你有同样的问题。CachedAsyncImage我通过编写一个与 保持相同 API 的解决方案AsyncImage,以便它们可以轻松互换,同时考虑到未来的本机缓存支持AsyncImage.

我做了一个Swift 包来分享它。

CachedAsyncImage具有与 完全相同的 API 和行为AsyncImage,因此您只需更改此设置:

AsyncImage(url: logoURL)
Run Code Online (Sandbox Code Playgroud)

对此:

CachedAsyncImage(url: logoURL)
Run Code Online (Sandbox Code Playgroud)

除了AsyncImage初始化器之外,您还可以指定要使用的缓存(默认情况下URLCache.shared使用):

CachedAsyncImage(url: logoURL, urlCache: .imageCache)
Run Code Online (Sandbox Code Playgroud)
// URLCache+imageCache.swift

extension URLCache {
    
    static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000)
}
Run Code Online (Sandbox Code Playgroud)

请记住,设置缓存时,响应(在本例中是我们的图像)不得大于磁盘缓存的 5% 左右(请参阅此讨论)。

这是回购协议

  • 这个库不起作用。我正在测试它,每次使用“List”时它都会获取远程图像 (8认同)
  • 完美解决方案 (2认同)

Rya*_*ung 13

希望这可以帮助其他人。我发现了这个很棒的视频,其中讨论了使用下面的代码来构建供您自己使用的异步图像缓存功能。

import SwiftUI

struct CacheAsyncImage<Content>: View where Content: View{
    
    private let url: URL
    private let scale: CGFloat
    private let transaction: Transaction
    private let content: (AsyncImagePhase) -> Content
    
    init(
        url: URL,
        scale: CGFloat = 1.0,
        transaction: Transaction = Transaction(),
        @ViewBuilder content: @escaping (AsyncImagePhase) -> Content
    ){
        self.url = url
        self.scale = scale
        self.transaction = transaction
        self.content = content
    }
    
    var body: some View{
        if let cached = ImageCache[url]{
            let _ = print("cached: \(url.absoluteString)")
            content(.success(cached))
        }else{
            let _ = print("request: \(url.absoluteString)")
            AsyncImage(
                url: url,
                scale: scale,
                transaction: transaction
            ){phase in
                cacheAndRender(phase: phase)
            }
        }
    }
    func cacheAndRender(phase: AsyncImagePhase) -> some View{
        if case .success (let image) = phase {
            ImageCache[url] = image
        }
        return content(phase)
    }
}
fileprivate class ImageCache{
    static private var cache: [URL: Image] = [:]
    static subscript(url: URL) -> Image?{
        get{
            ImageCache.cache[url]
        }
        set{
            ImageCache.cache[url] = newValue
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


val*_*ine 10

也许稍后会参加聚会,但我遇到了这个确切的问题,即 AsyncImage 与 ScrollView / LazyVStack 布局结合使用时性能不佳。

根据此帖子,该问题似乎在某种程度上是由于苹果当前的实施造成的,并且在未来的某个时候它将得到解决。

我认为我们可以使用的最面向未来的方法类似于Ryan Fung 的响应,但不幸的是,它使用旧语法并且错过了重载的 init(带或不带占位符)。

我扩展了该解决方案,涵盖了GitHub 的 Gist上缺失的案例。您可以像当前的 AsyncImage 实现一样使用它,这样当它一致支持缓存时,您可以将其交换出来。

  • 尝试了所有答案,这是第一个开箱即用的答案。谢谢!iOS 16.4、Xcode 14.3 (3认同)

I. *_*dan 6

AsyncImage在底层使用默认的URLCache 。管理缓存最简单的方法是更改​​默认URLCache的属性

URLCache.shared.memoryCapacity = 50_000_000 // ~50 MB memory space
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
Run Code Online (Sandbox Code Playgroud)

  • 你有消息来源证实这一点吗?所有其他答案都意味着没有缓存,您应该自己添加它。 (13认同)
  • 我用 Proxyman 进行了测试,没有缓存,即使定义了大缓存,所有请求也会重复发送!有很多人声称有缓存 - 但这只是 (4认同)
  • 文档状态:“此视图使用共享的 URLSession 实例从指定的 URL 加载图像,然后显示它。” 但是当查看我的应用程序的缓存文件夹时,我没有看到缓存的图像,但我确实看到了其他缓存的响应,所以我对该声明表示怀疑。 (3认同)
  • @stromyc实际上开发人员文档明确指出AsyncImage使用共享URLSession。此外我已经测试了这个并且这个有效。 (2认同)