Swift 在设计上,主要设计了三种多态类型,类型设计的基础是 Protocol,先定义一个 Protocol:

swift
protocol Shape {
    func draw() -> String
}

1、泛型(Generic)

swift 的泛型和 rust 等语言一致,属于编译期泛型,在编译期间替换为具体类型,无运行时开销。相当于模版;目的:开发者可以设计实现用更少的代码实现更多的功能。<T: SomeType>表示是一个泛型限定。

swift
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 协议里的关联类型-编译期确定具体类型

swift
protocol Shape {associatedtypeResultfuncdraw() ->Result}

可以在 Protocol 中指定一个泛型 associatedtype 类型,实现方实现的时候指定确定的类型,实现方有两种方式实现:

1.1.1 使用 typealias 指定具体类型

swift
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 指定:

swift
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 子句限制关联类型:

swift
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 在内部限定

swift
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 类型)

运行时类型,动态派发,更灵活,但有运行时开销:

swift
protocol Shape {
    func draw() -> String
}

func makeShape(_ type: Int) -> any Shape {
    if type == 0 {
        return Triangle()
    } else {
        return Square()
    }
}

makeShape返回一个协议类型 Shape,可能是 Triangle 也可能是 Square,需要运行时才能确定。所以不能把一个协议类型赋给一个泛型:

swift
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 团队发现,这种隐式的存在类型写法,和泛型很难区分,会在泛型与协议混用时造成大量困惑,比如:

swift
func process<T: Shape>(_ value: T) { ... }   // 泛型约束
func process(_ value: Shape) { ... }         // 存在类型(动态)

看起来只差一点,但性能、行为完全不同!于是 Swift 设计组决定:

✅ 从 Swift 5.6 开始,存在类型必须显式写成 any Shape,否则容易误导。
swift
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)

编译时类型,自己知道是什么类型,对调用者隐藏细节。

swift
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 的类型”;
  • 对编译器来说,它依然是静态已知的具体类型;
  • 对性能来说,没有动态分发的开销。

总结

Unsupported Notion block: table