从 S3 下载图像并使用 SwiftUI 显示它们会导致高内存使用

问题描述

嘿? 所以我正在从 AWS S3 下载图像并使用 swiftUI LazyVGrid 在我的应用程序中显示它们。

我要下载的代码如下:

class S3CacheFetcher: Fetcher {
    
    typealias KeyType = MediaItemCacheInfo
    typealias OutputType = NSData
    
    func get(_ key: KeyType) -> AnyPublisher<OutputType,Error> {
        return download(mediaItem: key).erasetoAnyPublisher()
    }
    
    private func download(mediaItem: KeyType) -> AnyPublisher<OutputType,Error>{
        let BUCKET = "someBucket"
        
        return Deferred {
            Future { promise in
                guard let key:String = S3CacheFetcher.getItemKey(mediaItem: mediaItem) else { fatalError("UserPoolID Error") }
                print("Downloading image with key: \(key)")
                AWSS3TransferUtility.default().downloadData(fromBucket: BUCKET,key: key,expression: nil) { (task,url,data,error) in
                    if let error = error{
                        print(error)
                        promise(.failure(error))
                    }else if let data = data{
// EDIT--------
                        let encrypt = S3CacheFetcher.encrypt(data: data)
                        let decrypt = S3CacheFetcher.decrypt(data: encrypt)
// EDIT--------
                        promise(.success(decrypt as NSData))

                    }
                }
            }
        }
        .erasetoAnyPublisher()
    }
....
// EDIT---------

// In my code I have a static function that decrypts the images using CryptoKit.AES.GCM

// To test my problem I added these two functions that should stand for my decryption.

    static var symmetricKey = SymmetricKey(size: .bits256)
    static func encrypt(data: Data) -> Data{
        return try! AES.GCM.seal(data,using: S3CacheFetcher.symmetricKey).combined!
    }
    
    static func decrypt(data: Data) -> Data{
        return try! AES.GCM.open(AES.GCM.SealedBox(combined: data),using: S3CacheFetcher.symmetricKey)
    }
}

我的网格视图:

struct AllPhotos: View {
    @StateObject var mediaManager = MediaManager()
    var body: some View {
      ScrollView{
          LazyVGrid(columns: columns,spacing: 3){
              ForEach(mediaManager.mediaItems) { item in
                  vstack{
                      ImageView(downloader: ImageLoader(mediaItem: item,size: .large,parentAlbum: nil))
                  }
              }
         }
    }
}

我在 GridView 中使用的 ImageView:

struct ImageView: View{
        @StateObject var downloader: ImageLoader
        
        var body: some View {
            Image(uiImage: downloader.image ?? UIImage(systemName: "photo")!)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .onAppear(perform: {
                    downloader.load()
                })
                .ondisappear {
                    downloader.cancel()
                }
        }
    }

最后但并非最不重要的是 ImageDownloader,它在显示图像视图时触发:

class ImageLoader: ObservableObject {
    @Published var image: UIImage?
    
    private(set) var isLoading = false
    
    private var cancellable: AnyCancellable?
    private(set) var mediaItem:MediaItem
    private(set) var size: ThumbnailSizes
    private(set) var parentAlbum: GetAlbum?
    
    init(mediaItem: MediaItem,size: ThumbnailSizes,parentAlbum: GetAlbum?) {
        self.mediaItem = mediaItem
        self.size = size
        self.parentAlbum = parentAlbum
    }
    
    deinit {
        cancel()
        self.image = nil
    }
    
    func load() {
        guard !isLoading else { return }
        
        // I use the Carlos cache library but for the sake of debugging I just use my Fetcher like below
        cancellable = S3CacheFetcher().get(.init(parentAlbum: self.parentAlbum,size: self.size,cipher: self.mediaItem.cipher,ivNonce: self.mediaItem.ivNonce,mid: self.mediaItem.mid))
            .map{ UIImage(data: $0 as Data)}
            .replaceError(with: nil)
            .handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },receiveCompletion: { [weak self] _ in self?.onFinish() },receiveCancel: { [weak self] in self?.onFinish() })
            .receive(on: dispatchQueue.main)
            .sink { [weak self] in self?.image = $0 }
    }
    
    func cancel() {
        cancellable?.cancel()
        self.image = nil
    }
    
    private func onStart() {
        isLoading = true
    }
    
    private func onFinish() {
        isLoading = false
    }
}

首先,在我描述我的问题之前。是的,我知道我必须缓存这些图像以获得更流畅的体验。我这样做了,但为了调试我的内存问题,我现在不缓存这些图像。

预期行为:下载图像并在显示视图时显示它们。如果未显示图像视图,则从内存中清除图像。

实际行为:下载图像并显示它们但一旦图像视图消失,它不会从内存中清除它们。如果我上下滚动一段时间内存使用量在 Gb 范围内上升并且应用程序崩溃。 如果我使用我的持久缓存从磁盘抓取图像与抓取和显示图像的逻辑或多或少相同,那么一切都按预期工作,并且内存使用量不高于 50 Mb。 >

我对 Combine 和 SwiftUI 还很陌生,因此非常感谢您的帮助。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...