SwiftUI NavigationView内存泄漏咨询:模态视图关闭后ViewModel未释放
这绝对是个值得探讨的有效问题,不少SwiftUI开发者都会在这个点上踩坑,完全不是你的知识储备不足!
问题背景
我尝试给模态展示的视图导航栏添加关闭按钮,但关闭后ViewModel的deinit方法始终未被调用。我发现问题出在navigationBarItems中对self的捕获,但由于View是结构体而非类,无法在navigationBarItems的action中传入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") } ) } } }
问题原因解析
虽然ModalView是结构体,但你的ViewModel是类类型(通过@ObservedObject修饰,说明它是ObservableObject的子类)。当你在navigationBarItems的Button action里引用self(即ModalView实例)时,会形成一条隐藏的引用链:
ModalView持有ViewModel的强引用- Button的闭包捕获了
ModalView实例 - NavigationView的内部结构可能会强引用这个闭包
最终导致ViewModel无法被释放,deinit自然不会被调用。
解决方案
这里有两种简洁的方式打破这个潜在的循环引用:
方案1:使用iOS 15+的@Environment(\.dismiss)(推荐)
苹果在iOS 15之后提供了更简洁的 dismissal API,无需引用self就能关闭视图:
struct ModalView: View { @Environment(\.dismiss) private var dismiss @ObservedObject var viewModel: ViewModel var body: some View { NavigationView { Text("Modal is presented") .navigationBarItems(leading: Button(action: { dismiss() }) { Text("close") } ) } } }
方案2:兼容iOS 14及以下的局部变量拆分
把presentation的内容提取为局部变量,避免闭包直接捕获整个ModalView实例:
struct ModalView: View { @Environment(\.presentationMode) private var presentation @ObservedObject var viewModel: ViewModel var body: some View { // 先把presentation的wrappedValue提取为局部变量 let presentationMode = presentation.wrappedValue NavigationView { Text("Modal is presented") .navigationBarItems(leading: Button(action: { presentationMode.dismiss() }) { Text("close") } ) } } }
这两种方式都能让闭包不再捕获整个ModalView,打破引用链,这样模态视图关闭后,ViewModel就能正常被释放,deinit方法也会被触发。
内容的提问来源于stack exchange,提问作者Vahagn Gevorgyan
相关产品推荐
相关产品推荐

