12-3.【OC】【Swift+OC 混编】为什么 Swift 泛型在 Objective-C 中消失?

5 阅读3分钟

在 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 的泛型逻辑,通常有两条路:

  1. 类型擦除 (Type Erasure) : 提供一个非泛型的子类。例如 class IntBox: Box<NSNumber>。这样 IntBox 就可以被 OC 识别。
  2. 包装器 (Wrapper) : 创建一个专门为 OC 准备的包装类,内部持有泛型实例,但对外暴露 idNSObject 类型的接口。

💡 深度启发

Swift 泛型的“消失”实际上保护了程序的稳定性。如果允许这种映射,那么你在 OC 中往一个 Box<String> 里塞进一个 NSNumber(OC 允许这么做),回到 Swift 端调用时就会直接导致内存损坏或非法指令崩溃。