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,再定义一个功能函数的时候,可能也需要额外处理:

上边代码 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) |
编译时确定具体类型 | ✅ 是 | ❌ 否 | ✅ 是 |
运行时动态分发 | ❌ 否 | ✅ 是 | ❌ 否 |
对外暴露内部类型 | ✅ 暴露 | ✅ 抹平但性能差 | ❌ 隐藏 |
使用场景 | 内部通用性 | 需要多态性 | 对外隐藏实现细节 |