11-4.【OC】【protocol】Swift protocol 中的 associatedtype 和泛型约束如何映射到 runtime?

5 阅读3分钟

Swift 的协议与 Objective-C 协议最大的不同在于:OC 协议是动态的、基于消息的,而带 associatedtype 的 Swift 协议(通常被称为 PATs, Protocols with Associated Types)是高度静态且基于类型证明的。

由于 OC Runtime 无法表达“关联类型”这种编译期概念,它们在 Swift Runtime 中通过 协议见证表(Protocol Witness Table)关联类型见证表 来实现。


1. 核心映射机制:协议见证表 (PWT)

当一个类型(Struct/Class)遵循一个带关联类型的协议时,Swift 编译器会为该类型生成一个 Protocol Witness Table (PWT)

  • PWT 的结构:它不仅包含协议方法的函数指针,还包含指向 关联类型元数据(Metadata) 的指针。
  • 运行时查找:在运行时,当程序通过协议类型调用方法时,它会查阅 PWT。对于关联类型,PWT 会提供一个入口,返回该特定实现中关联类型的真实 Target Type(如 IntString)。

2. 关联类型见证 (Associated Type Witnesses)

关联类型并不是简单的类型别名,它们在 Runtime 是一组 Witness(见证)

  • 见证表嵌套:如果 associatedtype 本身还有约束(例如 associatedtype Item: Equatable),那么 PWT 中对应的条目不仅指向 Item 的元数据,还会指向 Item 遵循 Equatable另一个 PWT
  • 递归证明:Runtime 通过这种嵌套的表结构,确保在泛型函数执行时,所有关于类型的假设(例如“这个 Item 可以比较”)在内存中都有对应的函数指针支持。

3. 泛型约束映射:要求要求 (Requirements)

当你写一个泛型函数 func process<T: MyProtocol>(item: T) 时,编译器会将约束映射为 隐式参数

  1. 参数传递:在底层,这个函数会被编译为接收两个参数:一个是 item 的指针,另一个是 T 遵循 MyProtocolPWT 地址
  2. 派发查找:函数内部不会使用 objc_msgSend,而是直接通过传入的 PWT 指针查找偏移量,获取方法的 IMP 内存地址。这种方式被称为 “静态派发的动态化”

4. 为什么映射不到 Objective-C?

这就是为什么带 associatedtype 的协议不能标记为 @objc 的底层原因:

  • 缺乏元数据插槽:OC 的 protocol_t 结构体只有方法列表和属性列表,没有预留“类型见证”或“关联类型描述”的位置。
  • 类型擦除冲突:Swift 的关联类型在运行时可能表现为不同的内存布局(Struct 可能占 8 字节,也可能占 64 字节),而 OC 的运行时要求所有对象都是 8 字节指针(id)。这种布局的不确定性导致桥接无法实现。

5. 总结:Runtime 映射对比

特性Swift 映射实现Runtime 表现形式
Associated TypeAssociated Type WitnessPWT 内部的一个元数据偏移量指针。
Generic ConstraintWitness Table Passing函数调用时作为隐式参数传递 PWT 地址。
Self 约束Self Type Metadata通过 Metadata 确认当前类型的真实大小和对齐方式。
Type ErasureExistential Container包含 3 个存储字、1 个 Metadata 指针、1 个 PWT 指针的容器。

💡 深度启发:泛型特化 (Specialization)

虽然运行时依赖 PWT 查找,但 Swift 编译器非常聪明。如果它能推断出具体的类型,它会进行 特化(Specialization) :直接生成一份专门针对 IntString 的函数拷贝,彻底移除 PWT 查找开销。