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