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

为何判断非类协议一致性会导致ViewController无法释放?

iOS内存泄漏踩坑:非类协议在Switch判断中的诡异问题

嘿,最近我踩了个特别诡异的iOS内存泄漏坑,花了一整天才排查出来,还做了最小复现示例,跟大家唠唠这个问题!

场景背景

我的项目里故事板结构很简单:一个UINavigationController,它的根视图控制器是ViewController,可以push跳转到AnotherViewController。

核心代码逻辑

先给大家看关键代码片段:
首先定义一个无约束的协议:

protocol MyProtocol {}

AnotherViewController遵循这个协议,并且加了deinit方法用来观察是否被释放:

class AnotherViewController: UIViewController, MyProtocol {
    deinit {
        print("🍏 Yippee!") // 打印日志确认释放状态
    }
}

ViewController作为UINavigationController的代理,实现了动画控制器相关方法,里面用switch判断两个视图控制器是否都符合MyProtocol(实际代码里从来没进入过这个分支):

class ViewController: UIViewController, UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        switch (fromVC, toVC) {
        case (let from as MyProtocol, let to as MyProtocol):
            // 实际运行中从未进入这个分支
            return nil
        default:
            return nil
        }
    }
}

诡异的运行现象

  • 点击Push按钮进入AnotherViewController,控制台输出🍏 Yippee!
  • 点击返回按钮回到ViewController,控制台再次输出🍏 Yippee!,但**AnotherViewController根本没被释放**——内存泄漏实锤了!

有效的修复方式

我试了三种方法,都能解决这个泄漏问题:

  • 把协议修改为类协议:protocol MyProtocol: class {}
  • 直接移除switch中判断是否符合MyProtocol的case分支
  • 让ViewController也遵循MyProtocol(这时会进入对应的case分支,泄漏也消失了)

关键疑问拆解

相信大家跟我一样好奇,为啥会出现这种奇葩情况?我查了Swift底层的代码生成逻辑,给大家拆解:

1. 为什么非类协议的switch判断会引发泄漏?

Swift里的非类协议(没有: class约束)可以被值类型和引用类型同时遵循。当你在switch的模式匹配里用as MyProtocol做类型转换时,编译器为了兼容值类型,会生成一个**存在容器(Existential Container)**来包装对象实例。这个容器会对AnotherViewController实例添加强引用,而且因为这个引用被导航代理的相关上下文持有,导致引用无法正常释放,最终造成内存泄漏。

2. 为什么类协议能解决问题?

当协议加上: class约束后,编译器明确知道只有类类型能遵循它,这时候做类型转换时不会生成存在容器,而是直接用类的引用方式处理,不会额外增加强引用,自然就不会有泄漏问题。

3. 为什么只有switch中的判断会出问题?

这是因为switch的模式匹配在处理非类协议的类型转换时,编译器的代码生成逻辑和if判断完全不同。用if let from = fromVC as? MyProtocol这种方式时,编译器的引用处理更“轻量”,不会像switch模式匹配那样生成持有强引用的存在容器,所以不会触发泄漏。同样,直接判断类类型(比如as? AnotherViewController)时,因为是明确的类类型,也不会有存在容器的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:06:14