在 Objective-C 中,你可能会发现 Swift 的泛型类变成了“空壳”或者干脆消失了。这背后的根本原因在于 Swift 的“强类型特化”设计 与 Objective-C 的“弱类型集合”设计 之间存在不可调和的架构冲突。
以下是导致泛型在桥接中“消失”的三个底层原因:
1. 静态派发 vs. 动态派发
- Swift 泛型(静态/双重派发) :Swift 编译器在处理泛型时,会根据 协议见证表 (PWT) 查找类型信息。为了性能,编译器往往会进行“泛型特化”,即为
Box<Int>和Box<String>生成两套不同的机器代码。 - Objective-C(完全动态派发) :OC 的运行时只认识
id(万能指针)。它在运行时通过消息传递(objc_msgSend)来查找方法,而不管对象的具体类型是什么。
冲突点:OC 的 Runtime 无法表达 Swift 这种基于编译期类型约束的派发逻辑。
2. 内存布局的不确定性
在 Objective-C 中,所有对象指针的大小是固定的(通常是 8 字节)。但在 Swift 中:
- 泛型占位符
T的大小是不确定的。一个T可能是占用 8 字节的类,也可能是占用 64 字节的结构体。 - 底层差异:Swift 可以在编译期或运行时通过
Metadata计算出T的偏移量。而 OC 的类结构要求其成员变量(ivar)的偏移量在类加载时就必须固定。
3. Objective-C “泛型”只是编译器的骗局
你可能会说:“Objective-C 不是有 NSArray<NSString *> * 吗?”
- 本质是类型擦除:OC 的泛型(轻量级泛型)只是给 编译器 看的语法糖,目的是在写代码时提供警告。
- 运行时真相:一旦代码运行起来,
NSArray<NSString *>里的所有信息都会消失,全部变回id。
为什么 Swift 泛型不能映射过去? Swift 的泛型是“真的”(持有类型元数据),而 OC 的泛型是“假的”(运行时只有指针)。Swift 不愿意将一个具有严格类型约束的泛型弱化为一个毫无约束的 id 对象,因为这会破坏 Swift 的类型安全承诺。
4. 映射规则的具体表现
当你在 Swift 中定义一个泛型类并尝试暴露给 OC 时:
Swift
// Swift
@objc class Box<T: AnyObject>: NSObject {
@objc var content: T?
}
在生成的 -Swift.h 桥接文件中,你会看到:
- 如果
T有具体的类限制,它可能映射为基础的NSObject。 - 如果
T是完全泛型的,这个类可能直接在.h中被忽略,或者该属性被标记为不可用(unavailable)。
如何在混编中解决这个问题?
如果你必须在 OC 中使用 Swift 的泛型逻辑,通常有两条路:
- 类型擦除 (Type Erasure) : 提供一个非泛型的子类。例如
class IntBox: Box<NSNumber>。这样IntBox就可以被 OC 识别。 - 包装器 (Wrapper) : 创建一个专门为 OC 准备的包装类,内部持有泛型实例,但对外暴露
id或NSObject类型的接口。
💡 深度启发
Swift 泛型的“消失”实际上保护了程序的稳定性。如果允许这种映射,那么你在 OC 中往一个 Box<String> 里塞进一个 NSNumber(OC 允许这么做),回到 Swift 端调用时就会直接导致内存损坏或非法指令崩溃。