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(如Int或String)。
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) 时,编译器会将约束映射为 隐式参数。
- 参数传递:在底层,这个函数会被编译为接收两个参数:一个是
item的指针,另一个是T遵循MyProtocol的 PWT 地址。 - 派发查找:函数内部不会使用
objc_msgSend,而是直接通过传入的 PWT 指针查找偏移量,获取方法的 IMP 内存地址。这种方式被称为 “静态派发的动态化” 。
4. 为什么映射不到 Objective-C?
这就是为什么带 associatedtype 的协议不能标记为 @objc 的底层原因:
- 缺乏元数据插槽:OC 的
protocol_t结构体只有方法列表和属性列表,没有预留“类型见证”或“关联类型描述”的位置。 - 类型擦除冲突:Swift 的关联类型在运行时可能表现为不同的内存布局(Struct 可能占 8 字节,也可能占 64 字节),而 OC 的运行时要求所有对象都是 8 字节指针(
id)。这种布局的不确定性导致桥接无法实现。
5. 总结:Runtime 映射对比
| 特性 | Swift 映射实现 | Runtime 表现形式 |
|---|---|---|
| Associated Type | Associated Type Witness | PWT 内部的一个元数据偏移量指针。 |
| Generic Constraint | Witness Table Passing | 函数调用时作为隐式参数传递 PWT 地址。 |
| Self 约束 | Self Type Metadata | 通过 Metadata 确认当前类型的真实大小和对齐方式。 |
| Type Erasure | Existential Container | 包含 3 个存储字、1 个 Metadata 指针、1 个 PWT 指针的容器。 |
💡 深度启发:泛型特化 (Specialization)
虽然运行时依赖 PWT 查找,但 Swift 编译器非常聪明。如果它能推断出具体的类型,它会进行 特化(Specialization) :直接生成一份专门针对 Int 或 String 的函数拷贝,彻底移除 PWT 查找开销。