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 已销毁
这说明两者都被系统正常回收,没有内存泄漏问题。
额外注意点
- 所有需要被
weak引用的协议都必须继承AnyObject,否则Swift会报错 - 在Presenter的异步任务中,建议用
[weak self]捕获自身,避免意外的循环引用 - 永远不要让Presenter强持有View层,这是MVP模式内存泄漏的核心根源
内容的提问来源于stack exchange,提问作者Zahid Usman Cheema
相关产品推荐
相关产品推荐

