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

SwiftUI结合VIP Clean架构的内存泄漏问题及修复咨询

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] = []
}
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,最终导致循环引用,视图销毁后这些对象无法被释放。

具体解决步骤

  1. 修改Presenter对View的引用为弱引用
    先确保MenuViewProtocol标记为类协议:

    protocol MenuViewProtocol: class {
        func update(sections: [MenuSection])
    }
    

    然后在Presenter中将View引用改为弱引用:

    class Presenter {
        weak var view: MenuViewProtocol?
        // ...其他代码
    }
    

    这一步能直接打破Presenter和View之间的强引用循环。

  2. 优化ViewModel的生命周期管理
    ObservedObject不需要一直存活,它的生命周期应该和绑定的View一致。如果ViewModel仅为当前View服务,建议在View内部用@StateObject创建(iOS 14及以上支持),代替外部传入:

    struct MenuView: View {
        @StateObject private var viewModel = MenuViewModel()
        // ...其他代码
    }
    

    这样SwiftUI会自动管理ViewModel的生命周期,View销毁时ViewModel也会被释放。如果必须外部传入,要确保没有其他无关强引用持有它。

  3. 确认闭包的引用捕获逻辑
    你已经在闭包中使用了[weak interactor],这部分是正确的,避免了View直接强引用Interactor。

  4. 用调试工具验证
    打开Xcode的Memory Graph Debugger(调试时点击工具栏的内存图标),查看对象的引用链,确认循环引用是否被解决,也能排查其他潜在的内存泄漏点。

内容的提问来源于stack exchange,提问作者ozzotto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:00:32