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

iOS中 dismiss 视图控制器时如何销毁MVP的Presenter对象

解决Swift MVP模式下Presenter与ViewController内存泄漏及weak声明崩溃问题

这个问题本质是MVP模式中循环引用导致的内存泄漏,以及错误的引用声明引发的空指针崩溃,咱们一步步拆解解决:

为什么会出现内存泄漏?

大概率是你当前的代码里,ViewController(View层)强持有Presenter,同时Presenter也强持有ViewController——这种双向强引用会形成循环引用链,导致两者的引用计数永远无法降到0,也就无法被系统销毁。

为什么把Presenter声明为weak会崩溃?

如果在ViewController里把Presenter设为weak,那Presenter实例没有任何强引用持有它,会在初始化后立即被释放,当你调用presenter.login(...)时,实际上是在访问一个已经被销毁的空对象,自然会崩溃。


正确的引用关系与实现方案

MVP模式下的标准引用逻辑应该是:

  • View层(ViewController)强持有Presenter:保证Presenter在View的生命周期内始终存在,不会被提前释放
  • Presenter弱持有View层(ViewController):避免形成循环引用,同时允许View被正常销毁

步骤1:定义带AnyObject约束的协议

因为weak只能用于类类型,所以View协议必须继承AnyObject:

// View层协议,约束View需要实现的方法
protocol LoginViewProtocol: AnyObject {
    func loginSuccess()
    func showLoginError(message: String)
}

// Presenter层协议,约束Presenter的行为
protocol LoginPresenterProtocol {
    func login(email: String, password: String)
    init(view: LoginViewProtocol)
}

步骤2:实现Presenter,用weak引用View

class LoginPresenter: LoginPresenterProtocol {
    // 关键:用weak修饰View引用,打破循环引用
    weak var view: LoginViewProtocol?
    
    required init(view: LoginViewProtocol) {
        self.view = view
    }
    
    func login(email: String, password: String) {
        // 模拟异步登录请求
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            guard let self = self else { return }
            // 登录结果回调View,用可选链避免空指针
            if email == "valid@example.com" && password == "123456" {
                DispatchQueue.main.async {
                    self.view?.loginSuccess()
                }
            } else {
                DispatchQueue.main.async {
                    self.view?.showLoginError(message: "账号或密码错误")
                }
            }
        }
    }
    
    deinit {
        print("LoginPresenter 已销毁")
    }
}

步骤3:ViewController强持有Presenter

class LoginViewController: UIViewController, LoginViewProtocol {
    // 关键:用强引用持有Presenter,保证其生命周期与View一致
    var presenter: LoginPresenterProtocol!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 初始化Presenter,将当前View传入
        presenter = LoginPresenter(view: self)
    }
    
    // 登录按钮点击事件
    @IBAction func loginButtonTapped(_ sender: UIButton) {
        presenter.login(email: "email", password: "password")
    }
    
    // 实现View协议方法
    func loginSuccess() {
        dismiss(animated: true)
    }
    
    func showLoginError(message: String) {
        let alert = UIAlertController(title: "错误", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "确定", style: .default))
        present(alert, animated: true)
    }
    
    deinit {
        print("LoginViewController 已销毁")
    }
}

验证是否正常销毁

当你调用dismiss(animated: true)关闭登录页面后,控制台会打印出:

LoginViewController 已销毁
LoginPresenter 已销毁

这说明两者都被系统正常回收,没有内存泄漏问题。

额外注意点

  1. 所有需要被weak引用的协议都必须继承AnyObject,否则Swift会报错
  2. 在Presenter的异步任务中,建议用[weak self]捕获自身,避免意外的循环引用
  3. 永远不要让Presenter强持有View层,这是MVP模式内存泄漏的核心根源

内容的提问来源于stack exchange,提问作者Zahid Usman Cheema

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:34:32