我正在尝试向模式显示的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")
}
)
}
}
}
答案 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)
}
}