使用 NavigationLink 推送视图时,已发布的 ViewModel 导致内存泄漏
Published ViewModel causes memory leak when pushing a view with NavigationLink
我之前问过 如何使用从异步回调接收到的数据推送视图。我最终使用的方法结果导致内存泄漏。
我正在尝试使用 SwiftUI 的 MVVM 构建我的应用程序,因此 ViewModel 应该发布另一个 ViewModel,然后 View 知道如何在屏幕上呈现。一旦呈现的视图从屏幕上消失,我希望相应的 ViewModel 被取消初始化。但是,提议的解决方案绝非如此。
在 UserView
被关闭后,我最终在内存中泄漏了一个 UserViewModel
的实例。 UserViewModel
从不打印 "Deinit UserViewModel",至少直到下次视图被推送到 pushUser
。
struct ParentView: View {
@ObservedObject var vm: ParentViewModel
var presentationBinding: Binding<Bool> {
.init(get: { vm.pushUser != nil },
set: { isPresented in
if !isPresented {
vm.pushUser = nil
}
}
)
}
var body: some View {
VStack {
Button("Get user") {
vm.getUser()
}
Button("Read user") {
print(vm.pushUser ?? "No userVm")
}
if let userVm = vm.pushUser {
NavigationLink(
destination: UserView(vm: userVm),
isActive: presentationBinding,
label: EmptyView.init
)
}
}
}
}
class ParentViewModel: ObservableObject {
@Published var pushUser: UserViewModel? = nil
var cancellable: AnyCancellable?
private func fetchUser() -> AnyPublisher<User, Never> {
Just(User.init(id: "1", name: "wiingaard"))
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
func getUser() {
cancellable = api.getUser().sink { [weak self] user in
self?.pushUser = UserViewModel(user: user)
}
}
}
struct User: Identifiable {
let id: String
let name: String
}
class UserViewModel: ObservableObject, Identifiable {
deinit { print("Deinit UserViewModel") }
@Published var user: User
init(user: User) { self.user = user }
}
struct UserView: View {
@ObservedObject var vm: UserViewModel
var body: some View {
Text(vm.user.name)
}
}
关闭 UserView
并检查调试内存图后,我看到 UserViewModel
的一个实例仍在分配中。
顶级引用 (view.content.vm
) 具有种类:(AnyViewStorage in fff57ab1a78)<ModifiedContent<UserView, (RelationshipModifier in fff57ad2760)<String>>>
和层次结构:SwiftUI.(AnyViewStorage in fff57ab1a78)<SwiftUI.ModifiedContent<MyApp.UserView, SwiftUI.(RelationshipModifier in fff57ad2760)<Swift.String>>> AnyViewStorageBase _TtCs12_SwiftObject
是什么原因导致此内存泄漏,我该如何删除它?
请参考这个post (),它通过在 NavigationView 上添加解决了 SwiftUI 中的内存泄漏问题:
.navigationViewStyle(StackNavigationViewStyle())
但是它破坏了动画,这个问题有一个 hacky 解决方案。出现动画问题是因为“if let”中的可选链接。
当在 NavigationLink 中将“nil”设置为目的地时,即使“presentationBinding”为真,它也基本上不会去任何地方。
我邀请您尝试这段代码,因为它修复了由 StackNavigationViewStyle 导致的动画问题(并且没有内存泄漏):
虽然不如可选链接那么漂亮,但它完成了工作。
如果您在 View 中使用 @State 并在 ViewModel 中收听 @Publisher,我可以看到 ViewModel 是 deinit()。
示例:
@State var showTest = false
NavigationLink(destination: SessionView(sessionViewModel: outgoingCallViewModel.sessionViewModel),
isActive: $showTest,
label: { })
.isDetailLink(false)
)
.onReceive(viewModel.$showView, perform: { show in
if show {
showTest = true
}
})
如果您在 NavigationLink 中将 viewModel.$show 用作 isActive,则 viewModel 永远不会 deinit()。
我之前问过
我正在尝试使用 SwiftUI 的 MVVM 构建我的应用程序,因此 ViewModel 应该发布另一个 ViewModel,然后 View 知道如何在屏幕上呈现。一旦呈现的视图从屏幕上消失,我希望相应的 ViewModel 被取消初始化。但是,提议的解决方案绝非如此。
在 UserView
被关闭后,我最终在内存中泄漏了一个 UserViewModel
的实例。 UserViewModel
从不打印 "Deinit UserViewModel",至少直到下次视图被推送到 pushUser
。
struct ParentView: View {
@ObservedObject var vm: ParentViewModel
var presentationBinding: Binding<Bool> {
.init(get: { vm.pushUser != nil },
set: { isPresented in
if !isPresented {
vm.pushUser = nil
}
}
)
}
var body: some View {
VStack {
Button("Get user") {
vm.getUser()
}
Button("Read user") {
print(vm.pushUser ?? "No userVm")
}
if let userVm = vm.pushUser {
NavigationLink(
destination: UserView(vm: userVm),
isActive: presentationBinding,
label: EmptyView.init
)
}
}
}
}
class ParentViewModel: ObservableObject {
@Published var pushUser: UserViewModel? = nil
var cancellable: AnyCancellable?
private func fetchUser() -> AnyPublisher<User, Never> {
Just(User.init(id: "1", name: "wiingaard"))
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
func getUser() {
cancellable = api.getUser().sink { [weak self] user in
self?.pushUser = UserViewModel(user: user)
}
}
}
struct User: Identifiable {
let id: String
let name: String
}
class UserViewModel: ObservableObject, Identifiable {
deinit { print("Deinit UserViewModel") }
@Published var user: User
init(user: User) { self.user = user }
}
struct UserView: View {
@ObservedObject var vm: UserViewModel
var body: some View {
Text(vm.user.name)
}
}
关闭 UserView
并检查调试内存图后,我看到 UserViewModel
的一个实例仍在分配中。
顶级引用 (view.content.vm
) 具有种类:(AnyViewStorage in fff57ab1a78)<ModifiedContent<UserView, (RelationshipModifier in fff57ad2760)<String>>>
和层次结构:SwiftUI.(AnyViewStorage in fff57ab1a78)<SwiftUI.ModifiedContent<MyApp.UserView, SwiftUI.(RelationshipModifier in fff57ad2760)<Swift.String>>> AnyViewStorageBase _TtCs12_SwiftObject
是什么原因导致此内存泄漏,我该如何删除它?
请参考这个post (
.navigationViewStyle(StackNavigationViewStyle())
但是它破坏了动画,这个问题有一个 hacky 解决方案。出现动画问题是因为“if let”中的可选链接。
当在 NavigationLink 中将“nil”设置为目的地时,即使“presentationBinding”为真,它也基本上不会去任何地方。
我邀请您尝试这段代码,因为它修复了由 StackNavigationViewStyle 导致的动画问题(并且没有内存泄漏):
虽然不如可选链接那么漂亮,但它完成了工作。
如果您在 View 中使用 @State 并在 ViewModel 中收听 @Publisher,我可以看到 ViewModel 是 deinit()。
示例:
@State var showTest = false
NavigationLink(destination: SessionView(sessionViewModel: outgoingCallViewModel.sessionViewModel),
isActive: $showTest,
label: { })
.isDetailLink(false)
)
.onReceive(viewModel.$showView, perform: { show in
if show {
showTest = true
}
})
如果您在 NavigationLink 中将 viewModel.$show 用作 isActive,则 viewModel 永远不会 deinit()。