@StateObject修饰的ViewModel关闭视图后未销毁、deinit不调用问题求助
问题原因
你遇到的@StateObject修饰的ViewModel无法释放问题,根因分为2种:
- 你使用的是iOS 13推出的旧版
NavigationViewAPI,它的导航栈默认会提前初始化所有NavigationLink的destination视图,且没有绑定导航状态的前提下,退栈时不会正确释放目标视图实例,依附于视图生命周期的@StateObject自然也不会被销毁。 - 如果你是在实际业务代码中出现这个问题,还需要排查ViewModel内部是否存在循环引用(比如持有闭包强引用self、持有外部对象反向强引用ViewModel等),你提供的极简示例不存在这个问题,所以是导航栈的问题。
解决方案
方案1:给NavigationLink绑定isActive状态(兼容iOS 13+)
给导航跳转加状态控制,退栈时主动置空状态,触发视图和ViewModel的销毁,修改后的ContentView代码如下:
import SwiftUI struct ContentView: View { // 加状态控制跳转 @State private var isShowMyView = false var body: some View { NavigationView { VStack{ NavigationLink(isActive: $isShowMyView, destination: { MySwiftUIView() }, label: { Text("NavigationLink") }) } }.navigationViewStyle(StackNavigationViewStyle()) } }
修改后点击返回按钮退栈,就能看到[x] deinit的打印。
方案2:使用iOS 16+的NavigationStack替代旧版NavigationView
新的导航栈API优化了生命周期管理,默认不会提前初始化destination,退栈会自动释放资源:
import SwiftUI struct ContentView: View { @State private var navPath = NavigationPath() var body: some View { NavigationStack(path: $navPath) { VStack{ Button("跳转") { navPath.append("MyView") } } .navigationDestination(for: String.self) { pathValue in if pathValue == "MyView" { MySwiftUIView() } } } } }
方案3:用LazyView包装destination(兼容旧版本)
如果不想改导航状态,可以用惰性包装器避免视图提前初始化,确保释放逻辑正确:
首先定义LazyView:
struct LazyView<Content: View>: View { let content: () -> Content init(@ViewBuilder _ content: @escaping () -> Content) { self.content = content } var body: some View { content() } }
然后修改NavigationLink的destination:
NavigationLink(destination: LazyView { MySwiftUIView() }) { Text("NavigationLink") }
内容的提问来源于stack exchange,提问作者D.D.
相关产品推荐
相关产品推荐

