1

嘿所以我正在从 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 还很陌生,因此非常感谢任何帮助。

4

0 回答 0