SwiftUI - NavigationView 中的内存泄漏

Vah*_*yan 13 memory-leaks ios swift swiftui combine

我正在尝试向模态呈现的视图导航栏添加一个关闭按钮。但是,在解雇后,我的视图模型deinit方法从未被调用。我发现问题在于它在navigationBarItem 中捕获self的位置。我不能只在navigationBarItem的动作中传递 a ,因为 View 是一个结构,而不是一个类。这是一个有效的问题还是只是缺乏知识?weak self

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")
                }

            )
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Tiz*_*neo 7

您不需要在其自己的视图中拆分关闭按钮。您可以通过向NavigationView 的闭包添加捕获列表来解决此内存泄漏:这将打破保留您的viewModel.

您可以在 Playground 中复制/粘贴此示例代码以查看它是否解决了问题(Xcode 11.4.1,iOS playground)。

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())
Run Code Online (Sandbox Code Playgroud)