【问题标题】:SwiftUI Grid Item Memory LeakSwiftUI 网格项内存泄漏
【发布时间】:2024-05-03 15:05:01
【问题描述】:

我正在尝试在 SwiftUI 中创建一个照片应用程序,以在设备上显示来自PHAsset 的照片。 但是,当我继续滚动网格视图时,内存会不断增长并最终由于内存问题而崩溃。

我也在从 RxSwfit 切换到 SwiftUI,所以如果有更好的方法来构建网格项及其视图模型,请提出建议。谢谢。

struct LibraryView: View {
    @ObservedObject var viewModel = ViewModel(photoLibraryService: PhotoLibraryService.shared)

    private var gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 2), count: 4)

    var body: some View {
        GeometryReader { gp in
            let width = CGFloat((Int(gp.size.width) - (2 * 3)) / 4)
            ScrollView {
                LazyVGrid(columns: gridItemLayout, spacing: 2) {
                    ForEach(viewModel.assets) { asset in
                        PhotoGridItem(asset: asset)
                            .frame(width: width, height: width)
                            .clipped()
                    }
                }
            }
        }
    }
}
struct PhotoGridItem: View {
    @ObservedObject var viewModel: ViewModel

    init(asset: LibraryAsset) {
        viewModel = ViewModel(asset: asset)
    }

    var body: some View {
        Image(uiImage: viewModel.image)
            .resizable()
            .scaledToFill()
            .clipped()
            .onAppear {
                viewModel.fetchImage()
                print("onAppear called")
            }.onDisappear {
                viewModel.cancelRequest()
                print("onDisappear called")
        }
    }
}

extension PhotoGridItem {
    class ViewModel: ObservableObject {
        @Published var image: UIImage = UIImage()
        var imageRequestID: PHImageRequestID?

        let asset: LibraryAsset

        init(asset: LibraryAsset) {
            self.asset = asset
        }

        // Note that the completion block below would be called multiple times
        // At first a smaller image would get returned first
        // Then it would return a clear image
        // Like it's progressively loading the image
        func fetchImage() {
            DispatchQueue.global(qos: .userInteractive).async {
                let requestOptions = PHImageRequestOptions()
                requestOptions.isNetworkAccessAllowed = true
                requestOptions.deliveryMode = .opportunistic

                PHImageManager.default().requestImage(
                    for: self.asset.asset,
                    targetSize: CGSize(width: 300, height: 300),
                    contentMode: .aspectFill,
                    options: requestOptions,
                    resultHandler: { [weak self] (image: UIImage?, info: [AnyHashable: Any]?) -> Void in
                        if let imageRequestID = info?[PHImageResultRequestIDKey] as? PHImageRequestID {
                            self?.imageRequestID = imageRequestID
                        }
                        DispatchQueue.main.async {
                            if let image = image {
                                self?.image = image
                            }
                        }
                    }
                )
            }
        }

        func cancelRequest() {
            image = UIImage()
            if let imageRequestID = imageRequestID {
                PHImageManager.default().cancelImageRequest(imageRequestID)
            }
        }
    }
}

【问题讨论】:

    标签: ios swift cocoa-touch swiftui combine


    【解决方案1】:

    你是对的。 LazyVGrid 就像它的名字一样工作。一个堆栈但懒惰。滚动时,我的布局基本相同,并且泄漏很大。为了缓解这种情况,我尝试将UIImage 设置为在视图消失时进行设置。但看起来View 中的Image 在消失后没有更新。所以泄漏是无法避免的。

    我用 20K 图像测试布局。从上到下滚动。只取照片但不设置到View。 RAM约为30MB。但是设置成View 后会得到大约 800MB。

    最后我回滚到UICollectionView

    这是我的布局。如果你有兴趣。

    https://github.com/MainasuK/JustPhotoCompress/blob/786b4bc1282747bdbb6c7e37f86a7f930c2044e6/PhotoCompress/Scene/PhotoGallery/View/PhotoThumbnailView.swift

    【讨论】: