SwiftUI - NavigationView 中的内存泄漏
Posted
技术标签:
【中文标题】SwiftUI - NavigationView 中的内存泄漏【英文标题】:SwiftUI - memory leak in NavigationView 【发布时间】:2020-08-01 20:01:24 【问题描述】:我正在尝试向模态显示的视图导航栏添加一个关闭按钮。但是,解雇后,我的视图模型 deinit 方法永远不会被调用。我发现问题在于它在 navigationBarItem 中捕获了 self。我不能只在 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")
)
【问题讨论】:
【参考方案1】:由于navigationBarItems
,我遇到了严重的内存泄漏,并将我的视图模型传递给我用作条形项目的视图。
四处挖掘,我了解到navigationBarItems
is deprecated
我有
.navigationBarItems(trailing:
AlbumItemsScreenNavButtons(viewModel: viewModel)
)
替换为toolbar
。
我现在的用法是这样的:
.toolbar
ToolbarItemGroup(placement: .navigationBarTrailing)
AlbumItemsScreenNavButtons(viewModel: viewModel)
【讨论】:
【参考方案2】:我的解决办法是
.navigationBarItems(
trailing: self.filterButton
)
..........................................
var filterButton: some View
Button(action: [weak viewModel] in
viewModel?.showFilter()
,label:
Image("search-filter-icon").renderingMode(.original)
)
【讨论】:
【参考方案3】:您不需要在自己的视图中拆分关闭按钮。您可以通过在 NavigationView 的闭包中添加 capture list 来解决此内存泄漏问题:这将破坏保留您的 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())
【讨论】:
【参考方案4】:我推荐设计级解决方案,即。将导航栏项目分解为单独的视图组件会破坏导致泄漏的不希望的循环引用。
使用 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()
【讨论】:
以上是关于SwiftUI - NavigationView 中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 弹出框在 NavigationView 中时消失
SwiftUI:检测 tvOS 上 NavigationView 中的选择更改