SwiftUI-NavigationView中的内存泄漏

时间:2020-04-19 11:19:58

标签: ios swift memory-leaks swiftui combine

我正在尝试向模式显示的View的导航栏添加一个关闭按钮。但是,关闭后,再也不会调用我的视图模型 deinit 方法。我发现问题在于它捕获了 navigationBarItem 中的自身。我不能只在 navigationBarItem 的操作中传递一个weak self,因为View是一个结构而不是一个类。这是一个有效的问题还是只是缺乏知识?

struct ModalView: View {

    @Environment(\.presentationMode) private var presentation: Binding<PresentationMode>
    @ObservedObject var viewModel: ViewModel

    var body: some View {

        NavigationView {
            Text("Modal is presented")
            .navigationBarItems(leading:
                Button(action: {
                    // works after commenting this line
                    self.presentation.wrappedValue.dismiss()
                }) {
                    Text("close")
                }

            )
        }
    }
}

4 个答案:

答案 0 :(得分:5)

您无需在自己的视图中拆分关闭按钮。您可以通过在NavigationView的闭包中添加capture list来解决此内存泄漏:这将破坏保留您的viewModel的参考周期。

您可以将此示例代码复制/粘贴到操场上,以查看它已解决了该问题(Xcode 11.4.1,iOS操场)。

import SwiftUI
import PlaygroundSupport

struct ModalView: View {
    @Environment(\.presentationMode) private var presentation
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        // Capturing only the `presentation` property to avoid retaining `self`, since `self` would also retain `viewModel`.
        // Without this capture list (`->` means `retains`):
        // self -> body -> NavigationView -> Button -> action -> self
        // this is a retain cycle, and since `self` also retains `viewModel`, it's never deallocated.
        NavigationView { [presentation] in
            Text("Modal is presented")
                .navigationBarItems(leading: Button(
                    action: {
                        // Using `presentation` without `self`
                        presentation.wrappedValue.dismiss()
                },
                    label: { Text("close") }))
        }
    }
}

class ViewModel: ObservableObject { // << tested view model
    init() {
        print(">> inited")
    }

    deinit {
        print("[x] destroyed")
    }
}

struct TestNavigationMemoryLeak: View {
    @State private var showModal = false
    var body: some View {
        Button("Show") { self.showModal.toggle() }
            .sheet(isPresented: $showModal) { ModalView(viewModel: ViewModel()) }
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.setLiveView(TestNavigationMemoryLeak())

答案 1 :(得分:2)

我建议设计级别的解决方案,即将导航栏项分解为单独的视图组件会中断不希望的循环,从而导致泄漏。

经过Xcode 11.4 / iOS 13.4的测试-ViewModel已按预期销毁。

以下是完整的测试模块代码:

struct CloseBarItem: View { // separated bar item with passed binding
    @Binding var presentation: PresentationMode
    var body: some View {
        Button(action: {
            self.presentation.dismiss()
        }) {
            Text("close")
        }
    }
}

struct ModalView: View {
    @Environment(\.presentationMode) private var presentation
    @ObservedObject var viewModel: ViewModel

    var body: some View {

        NavigationView {
            Text("Modal is presented")
            .navigationBarItems(leading: 
                CloseBarItem(presentation: presentation)) // << decompose
        }
    }
}

class ViewModel: ObservableObject {    // << tested view model
    init() {
        print(">> inited")
    }

    deinit {
        print("[x] destroyed")
    }
}

struct TestNavigationMemoryLeak: View {
    @State private var showModal = false
    var body: some View {
        Button("Show") { self.showModal.toggle() }
            .sheet(isPresented: $showModal) { ModalView(viewModel: ViewModel()) }
    }
}

struct TestNavigationMemoryLeak_Previews: PreviewProvider {
    static var previews: some View {
        TestNavigationMemoryLeak()
    }
}

答案 2 :(得分:1)

我的解决方法是

.navigationBarItems(
    trailing: self.filterButton
)

..........................................

var filterButton: some View {
    Button(action: {[weak viewModel] in
        viewModel?.showFilter()
    },label: {
        Image("search-filter-icon").renderingMode(.original)
    })
}

答案 3 :(得分:1)

由于 navigationBarItems 和将我的视图模型传递给我用作栏项目的视图,我遇到了严重的内存泄漏。

深入研究,我了解到 navigationBarItems is deprecated

我有

        .navigationBarItems(trailing:
            AlbumItemsScreenNavButtons(viewModel: viewModel)
        )

替换为 toolbar

我现在的使用情况如下:

        .toolbar {
            ToolbarItemGroup(placement: .navigationBarTrailing) {
                AlbumItemsScreenNavButtons(viewModel: viewModel)
            }
        }