SwiftUI结合VIP Clean架构的内存泄漏问题及修复咨询
问题背景
刚接触SwiftUI,在现有代码库中引入SwiftUI并沿用全项目的VIP Clean架构,除了View从类改为结构体、模块搭建需调整外整体流程顺畅,但发现视图消失后部分类型未被释放,存在潜在内存泄漏问题。
流程说明:视图onAppear时调用interactor,经presenter回调视图的update(sections:)方法更新数据。当前疑问:是否因使用ObservedObject导致无法释放?或是ObservedObject本就需在应用运行期一直存活?
模块搭建代码
class Module { func createView() -> some View { let interactor = Interactor() let presenter = Presenter() let view = MenuView(viewModel: MenuViewModel(), interactor: interactor) interactor.presenter = presenter presenter.view = view return view } }
视图类型定义
struct MenuOption: Hashable, Identifiable { let id: UUID = UUID() let title: String let isSelected: Bool } struct MenuSection: Hashable, Identifiable { let id: UUID = UUID() let items: [MenuOption] } class MenuViewModel: ObservableObject { @Published var sections: [MenuSection] = [] }
MenuView实现代码
struct MenuView: View { @ObservedObject var viewModel: MenuViewModel let interactor: InteractorProtocol var body: some View { Menu { ForEach(viewModel.sections.indices, id: \.self) { sectionIndex in let section = viewModel.sections[sectionIndex] Section { ForEach(section.items.indices, id: \.self) { itemIndex in let item = section.items[itemIndex] Button { [weak interactor] in interactor?.didSelect(index: itemIndex, section: sectionIndex) } label: { Label(item.title, systemImage: item.isSelected ? "checkmark" : "") } } } } } label: { Image(systemName: "name") } .menuOrder(.fixed) .onAppear { [weak interactor] in interactor?.configure() } } } extension MenuView: MenuViewProtocol { func update(sections: [MenuSection]) { self.viewModel.sections = sections } }
问题分析与解决方案
内存泄漏核心原因
你的问题和ObservedObject本身无关,核心是Presenter与View之间的强引用循环:
- SwiftUI的View是结构体,但当它实现
MenuViewProtocol(类协议)时,会被自动包装成堆上的Box对象; - Module中你将
MenuView实例赋值给presenter.view,如果Presenter中对View的引用是强引用,就会形成Interactor -> Presenter -> View(包装类)的强引用链,而View通过闭包间接关联Interactor,最终导致循环引用,视图销毁后这些对象无法被释放。
具体解决步骤
修改Presenter对View的引用为弱引用
先确保MenuViewProtocol标记为类协议:protocol MenuViewProtocol: class { func update(sections: [MenuSection]) }然后在Presenter中将View引用改为弱引用:
class Presenter { weak var view: MenuViewProtocol? // ...其他代码 }这一步能直接打破Presenter和View之间的强引用循环。
优化ViewModel的生命周期管理
ObservedObject不需要一直存活,它的生命周期应该和绑定的View一致。如果ViewModel仅为当前View服务,建议在View内部用@StateObject创建(iOS 14及以上支持),代替外部传入:struct MenuView: View { @StateObject private var viewModel = MenuViewModel() // ...其他代码 }这样SwiftUI会自动管理ViewModel的生命周期,View销毁时ViewModel也会被释放。如果必须外部传入,要确保没有其他无关强引用持有它。
确认闭包的引用捕获逻辑
你已经在闭包中使用了[weak interactor],这部分是正确的,避免了View直接强引用Interactor。用调试工具验证
打开Xcode的Memory Graph Debugger(调试时点击工具栏的内存图标),查看对象的引用链,确认循环引用是否被解决,也能排查其他潜在的内存泄漏点。
内容的提问来源于stack exchange,提问作者ozzotto

