11-3.【OC】【protocol】Swift Protocol 与 OC Protocol 的桥接原理是什么?

4 阅读3分钟

Swift 与 Objective-C (OC) Protocol 的桥接是 iOS 混编开发的核心。其底层原理可以概括为 “编译器自动合成转换器(Thunking)”“运行时元数据对齐”

由于 Swift 的协议支持泛型和关联类型(Static Dispatch),而 OC 协议纯粹依赖消息转发(Dynamic Dispatch),两者之间存在巨大的鸿沟。


1. 桥接的前提:@objc 关键字

在 Swift 中,一个协议要能被 OC 识别,必须满足以下条件:

  • 使用 @objc 标记。
  • 协议必须是 类类型(Class-only) ,即继承自 AnyObject 或其他 OC 协议。
  • 协议中的成员(属性、方法)必须能被 OC 表达(例如不能有 Swift 独有的元组、泛型、枚举等)。

2. 核心原理:双向视图 (Witness Table vs. Method List)

Swift 和 OC 对“协议实现”的存储方式截然不同:

  • OC 协议:实现逻辑直接存在于类的 method_list 中。
  • Swift 协议:实现逻辑存在于 协议见证表 (Protocol Witness Table, PWT) 中。

当 OC 调用 Swift 实现的协议方法时:

  1. 编译器介入:Swift 编译器会为实现该协议的 Swift 类自动生成一个 “OC 可见的方法(Thunk)”
  2. 存入方法列表:这个 Thunk 方法会被注册到该类的 OC 运行时方法列表中。
  3. 消息转发:当 OC 调用 [obj performProtocolMethod] 时,通过标准的 objc_msgSend 找到这个 Thunk。
  4. 内部跳转:Thunk 内部会负责处理参数转换(如 StringNSString),然后跳转到真正的 Swift 实现。

3. 运行时的元数据映射

当你在混编环境中使用 conformsToProtocol: 时,底层发生了什么?

  • OC 视角:OC 运行时只认它在全局哈希表中注册的 protocol_t
  • Swift 视角:Swift 运行时拥有更复杂的协议描述符。

桥接过程

当你给 Swift 协议加上 @objc 时,Swift 编译器会同时在二进制文件的特定段(Section)生成一个对应的 OC 协议元数据。这样,当 objc_getProtocol("MyProtocol") 被调用时,Runtime 能够找到对应的结构体,使 isKind(of:)conforms(to:) 逻辑在双向环境下都能成立。


4. 关键局限:为什么 Swift 独有特性无法桥接?

这就是为什么带 关联类型(associatedtype) 的 Swift 协议无法标记为 @objc 的原因:

  • 关联类型依赖于编译时的类型擦除(Type Erasure) :OC 的消息分发无法在运行时确定这些类型的大小或布局。
  • 静态派发 vs 动态派发:Swift 协议默认可以通过静态派发优化,但 @objc 强迫协议回退到动态派发模式,这会牺牲一定的性能。

5. 总结:桥接前后的变化

特性纯 Swift Protocol桥接后的 @objc Protocol
派发方式主要是 PWT 派发,支持静态优化完全依赖 objc_msgSend
可选方法只能通过协议扩展模拟支持原生 @optional
内存限制可用于 Struct / Enum / Class仅限 Class
关联类型✅ 支持❌ 不支持

💡 深度启发:桥接的选择

如果你在做一个纯 Swift 框架,千万不要为了兼容而到处写 @objc,因为这会让你失去 Swift 的面向协议编程 (POP) 中最重要的泛型能力。