XiaoboTalk

Swift 编译期泛型T/Boxed协议类型 any /Opaque不透明类型 some

Swift 在设计上,主要设计了三种多态类型,类型设计的基础是 Protocol,先定义一个 Protocol:
protocol Shape { func draw() -> String }

1、泛型(Generic)

swift 的泛型和 rust 等语言一致,属于编译期泛型,在编译期间替换为具体类型,无运行时开销。相当于模版;目的:开发者可以设计实现用更少的代码实现更多的功能。<T: SomeType>表示是一个泛型限定。
protocol Shape { func draw() -> String } struct Triangle: Shape { var size: Int func draw() -> String { (1...size).map { String(repeating: "*", count: $0) }.joined(separator: "\n") } } struct FlippedShape<T: Shape>: Shape { var shape: T func draw() -> String { shape.draw().split(separator: "\n").reversed().joined(separator: "\n") } } // 工厂:返回具体泛型类型 FlippedShape<T> func makeFlippedGeneric<T: Shape>(_ s: T) -> FlippedShape<T> { FlippedShape(shape: s) } let tri = Triangle(size: 3) let flippedGeneric = makeFlippedGeneric(tri) // flippedGeneric 的类型是 FlippedShape<Triangle> —— caller 知道确切类型 print(type(of: flippedGeneric)) // FlippedShape<Triangle> print(flippedGeneric.draw())

1.1 associatedtype 协议里的关联类型-编译期确定具体类型

protocol Shape {associatedtypeResultfuncdraw() ->Result}
可以在 Protocol 中指定一个泛型 associatedtype 类型,实现方实现的时候指定确定的类型,实现方有两种方式实现:

1.1.1 使用 typealias 指定具体类型

struct Triangle: Shape { typealias Result = String var size: Int func draw() -> Result { (1...size).map { String(repeating: "*", count: $0) }.joined(separator: "\n") } }

1.1.2 让编译器自己推断

编译器也可以自己推断类型,无需 typealias 指定:
struct Triangle: Shape { var size: Int func draw() -> String { (1...size).map { String(repeating: "*", count: $0) }.joined(separator: "\n") } }

1.1.3 使用 where 子句限制 typealias 的具体类型

如果使用了 typealias,再定义一个功能函数的时候,可能也需要额外处理:
notion image
上边代码 FlippedShape 有个属性是 shape 类型,内部需要对这个属性进行操作,但是 shape.draw() 返回的是 associatedtype 关联类型。所以无法调用任何方法,此时可以使用 where 子句限制关联类型:
struct FlippedShape<T: Shape>: Shape where T.Result == String { var shape: T func draw() -> String { let r = shape.draw().split(separator: "\n").reversed().joined(separator: "\n") return r; } }
注意:where 子句在这种场景只能用于泛型。

1.1.4 包装一层实现类型擦除: 使用 where 在内部限定

struct AnyShape: Shape { typealias Result = String let _draw: () -> Result init<T: Shape>(_ input: T) where T.Result == String { _draw = input.draw; } func draw() -> Result { return _draw(); } } func typeRemove() { let t = Triangle(size: 3) let anyShape: AnyShape = AnyShape(t); }
注意,这里将泛型定义在 init 函数上,而不是 AnyShape struct 本身

2、协议类型(Existential 协议存在类型,Boxed 类型)

运行时类型,动态派发,更灵活,但有运行时开销:
protocol Shape { func draw() -> String } func makeShape(_ type: Int) -> any Shape { if type == 0 { return Triangle() } else { return Square() } }
makeShape返回一个协议类型 Shape,可能是 Triangle 也可能是 Square,需要运行时才能确定。所以不能把一个协议类型赋给一个泛型
func makeShape(_ type: Int) -> Shape { if type == 0 { return Triangle() } else { return Square() } } // 工厂:返回具体泛型类型 FlippedShape<T> func makeFlippedGeneric<T: Shape>(_ s: T) -> FlippedShape<T> { FlippedShape(shape: s) } let protocolType = makeShape(); // let tttt = makeFlippedGeneric(makeShape()); // 编译报错 ❌,无法得知 makeShape 的具体类型
makeFlippedGeneric 是个泛型函数,需要在编译时就确定参数的具体类型,而 makeShape 是个协议类型,直到运行时才知道具体类型,所以无法赋值。

2.1 Swift 5.6 引入 any Shape 显式表面是类型存在类型

Swift 团队发现,这种隐式的存在类型写法,和泛型很难区分,会在泛型与协议混用时造成大量困惑,比如:
func process<T: Shape>(_ value: T) { ... } // 泛型约束 func process(_ value: Shape) { ... } // 存在类型(动态)
看起来只差一点,但性能、行为完全不同!于是 Swift 设计组决定:
✅ 从 Swift 5.6 开始,存在类型必须显式写成 any Shape,否则容易误导。
func draw(_ shape: Shape) { ... } // ❌ 警告(未来版本不推荐) func draw(_ shape: any Shape) { ... } // ✅ 明确表示是存在类型
在 Swift 5.6–5.9 之间:
  • 不写 any 仍然能编译,但会出现警告提示:“Implicitly using the existential ‘Shape’ as a generic constraint is deprecated; use ‘any Shape’ instead.”
在 Swift 6(即将正式发布) 中:
  • 必须写 any,否则会报错。

3、不透明类型 (Opaque Type)

编译时类型,自己知道是什么类型,对调用者隐藏细节。
public func makeComplexShape() -> some Shape { let smallTriangle = Triangle(size: 3) let flipped = FlippedShape(shape: smallTriangle) return JoinedShape(top: smallTriangle, bottom: flipped) }
这表示:
我(函数实现者)知道返回的具体类型是什么(JoinedShape<Triangle, FlippedShape<Triangle>>),
但我不告诉你(调用者),只保证它符合 Shape 协议。
所以:
  • 对调用者来说,它只是“某种符合 Shape 的类型”;
  • 对编译器来说,它依然是静态已知的具体类型;
  • 对性能来说,没有动态分发的开销。

总结

特性
泛型 (T: Shape)
协议类型 (any Shape)
不透明类型 (some Shape)
编译时确定具体类型
✅ 是
❌ 否
✅ 是
运行时动态分发
❌ 否
✅ 是
❌ 否
对外暴露内部类型
✅ 暴露
✅ 抹平但性能差
❌ 隐藏
使用场景
内部通用性
需要多态性
对外隐藏实现细节