You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI NavigationView内存泄漏咨询:模态视图关闭后ViewModel未释放

模态视图关闭后ViewModel的deinit未调用:NavigationBarItems闭包捕获的坑

这绝对是个值得探讨的有效问题,不少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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 11:43:12