# 扩展

Source: https://codewiki.com/zh/swift/extensions/

> - **what**: 扩展（Swift extension）在原类型声明之外添加计算属性、方法、构造器、下标、嵌套类型或协议遵循，不需要修改原始源码。
> - **trap**: 扩展不能添加存储属性或重写已有成员；协议扩展里未声明为要求的方法，通过协议类型调用时也不会动态选择具体类型的同名实现。
> - **fix**: 把需要多态分派的成员写进协议要求，用 `where` 精确限制扩展，并尽量让类型或协议至少有一方属于当前模块。

## 是什么，为什么存在

Swift 扩展是在类型原始声明之外补充功能的声明。它能扩展类、结构体、枚举和协议，也能扩展标准库或依赖库中的类型。扩展声明的成员在调用点表现为该类型的成员，而不是需要额外包装对象的工具函数。

扩展解决了两个不同问题。对于自己维护的类型，它能按职责拆分实现，例如把协议遵循与核心状态分开。对于不能修改源码的类型，它能添加适合当前模块的便捷操作，或让该类型遵循当前模块定义的协议。

扩展可以添加以下声明：

- 计算实例属性和计算类型属性；
- 实例方法和类型方法，包括值类型的 `mutating` 方法；
- 构造器、下标与嵌套类型；
- 协议遵循，以及满足协议要求的实现。

扩展不会重新打开类型的存储布局。它不能添加存储属性或属性观察器，也不能添加析构器、覆盖已有实现或为类增加父类。类扩展只能添加便利构造器，不能添加指定构造器。

当一个行为需要新的状态、不变量或独立身份时，包装类型通常比扩展更合适。扩展适合从接收者现有状态计算结果，或把已有能力组织成更清楚的接口；它不是绕过类型设计边界的后门。

## 工作原理

扩展使用 `extension TypeName` 声明。编译器在类型检查时把可见的扩展成员纳入成员查找，但原类型声明和扩展仍可能位于不同文件或模块。调用方必须导入声明扩展的模块，扩展才在该源文件中可见。

扩展成员遵循普通的访问控制。与原类型位于同一文件的扩展可以访问该类型的 `private` 成员；移动到另一文件后，这种访问不再成立。给扩展标记访问级别，会为其中未显式标记的成员提供默认访问级别，但不能突破原声明的可见性。

受约束扩展把成员的可用性与类型条件绑定。`extension Array where Element == String` 只给字符串数组添加成员；`Element: Hashable` 则接受任何满足该协议的元素类型。这种条件遵循（conditional conformance）或条件成员不会在运行时试探条件，编译器会在调用点证明约束是否满足。

协议扩展有两类成员，分派规则不同。如果成员本来就是协议要求，扩展可以提供默认实现，具体类型自己的实现会作为协议见证（protocol witness）参与调用。如果成员只存在于扩展中，通过协议类型访问时会根据静态类型选择扩展实现；具体类型中的同名方法不是覆盖。

协议遵循是整个进程可见的关系，不是局部别名。当扩展同时为其他模块的类型遵循其他模块的协议时，就形成追溯遵循（retroactive conformance）。Swift 6 会对此给出警告，因为任一上游模块以后加入同一遵循，都可能造成冲突；`@retroactive` 只表示你明确接受风险，不会消除风险。

## 示例

下面三个独立程序依次展示普通成员、条件能力和协议扩展分派。每段代码都只依赖 Swift 标准库，可以直接保存为文件并运行。

### 为现有状态添加计算与修改操作

`Temperature` 保留摄氏温度作为唯一存储。扩展从该状态计算华氏温度，并用 `mutating` 方法更新值；集合扩展则利用已有的 `indices` 提供安全下标。

<!-- quick -->

```swift
struct Temperature {
    var celsius: Double
}

extension Temperature {
    var fahrenheit: Double {
        celsius * 9 / 5 + 32
    }

    mutating func clamp(to range: ClosedRange<Double>) {
        celsius = min(max(celsius, range.lowerBound), range.upperBound)
    }
}

extension Collection {
    subscript(safe index: Index) -> Element? {
        indices.contains(index) ? self[index] : nil
    }
}

var reading = Temperature(celsius: 150)
reading.clamp(to: -40...40)
print(reading.celsius, reading.fahrenheit)

let sensors = ["north", "south", "west"]
print(sensors[safe: 1] ?? "missing")
print(sensors[safe: 9] ?? "missing")
```

```text
40.0 104.0
south
missing
```

<!-- /quick -->

计算属性没有为每个实例增加新字段，`fahrenheit` 每次都从 `celsius` 求值。`clamp` 必须标记为 `mutating`，因为它修改结构体的 `self`。通用下标使用集合自己的 `Index`，所以不仅适用于以 `Int` 为索引的数组。

安全下标不会替代所有越界错误。有些 API 的索引失效表示调用方破坏了不变量，此时返回 `nil` 反而会掩盖缺陷。只有“位置可能不存在”本来就是契约的一部分时，才应提供这种可选结果。

### 用条件扩展表达能力

`Batch` 对所有元素类型都能报告是否为空。只有元素可比较时，它才提供 `contains` 并遵循 `Equatable`，因此调用点不需要运行时转换。

```swift
struct Batch<Element> {
    let values: [Element]
}

extension Batch {
    enum State {
        case empty
        case ready
    }

    var state: State {
        values.isEmpty ? .empty : .ready
    }
}

extension Batch where Element: Equatable {
    func contains(_ candidate: Element) -> Bool {
        values.contains(candidate)
    }
}

extension Batch: Equatable where Element: Equatable {}

let first = Batch(values: ["A-1", "B-2"])
let second = Batch(values: ["A-1", "B-2"])
print(first.contains("B-2"))
print(first == second)

switch first.state {
case .empty: print("empty")
case .ready: print("ready")
}
```

```text
true
true
ready
```

空状态不依赖 `Element` 的能力，所以它属于无条件扩展。`contains` 需要相等比较，约束只放在提供该方法的扩展上。`Equatable` 遵循也带有相同条件，`Batch` 因而仍可使用，只是不能比较。

嵌套的 `State` 名称属于 `Batch` 的命名空间，但它不捕获外层泛型实参。若嵌套类型本身需要保存 `Element`，应在自己的存储或泛型声明中明确表达，而不是假定外层实例存在。

### 区分协议要求与扩展成员

`render()` 是协议要求，`debugLabel()` 只由扩展添加。`Audit` 为两者都声明同名实现后，协议类型上的两个调用表现不同。

```swift
protocol Reportable {
    var title: String { get }
    func render() -> String
}

extension Reportable {
    func render() -> String {
        "Default: \(title)"
    }

    func debugLabel() -> String {
        "[report] \(title)"
    }
}

struct Audit: Reportable {
    let title: String

    func render() -> String {
        "Audit: \(title)"
    }

    func debugLabel() -> String {
        "[audit] \(title)"
    }
}

let concrete = Audit(title: "Access")
let erased: any Reportable = concrete
print(erased.render())
print(concrete.debugLabel())
print(erased.debugLabel())
```

```text
Audit: Access
[audit] Access
[report] Access
```

`erased.render()` 使用 `Audit` 的实现，因为 `render` 是协议要求，遵循记录保存了对应见证。`concrete.debugLabel()` 根据具体静态类型找到 `Audit` 的成员。`erased.debugLabel()` 的静态类型只有 `any Reportable`，因此选择协议扩展成员。

如果调用方需要每个遵循类型自定义 `debugLabel()`，就应把它加入协议声明，并在扩展中保留默认实现。仅仅给具体类型写一个同名方法，不会把扩展成员变成协议要求。

### 添加构造器与嵌套类型

扩展构造器仍必须完成原类型的全部初始化规则。下面的扩展调用结构体已有的成员构造器，而嵌套枚举只负责描述从现有存储计算出的形状。

```swift
struct Rectangle {
    let width: Int
    let height: Int
}

extension Rectangle {
    enum Shape {
        case square
        case oblong
    }

    init(square side: Int) {
        self.init(width: side, height: side)
    }

    var shape: Shape {
        width == height ? .square : .oblong
    }
}

extension Rectangle: CustomStringConvertible {
    var description: String {
        "\(width)x\(height)"
    }
}

let tile = Rectangle(square: 4)
let banner = Rectangle(width: 8, height: 3)
print(tile)
print(banner)

switch tile.shape {
case .square: print("square")
case .oblong: print("oblong")
}
```

```text
4x4
8x3
square
```

把自定义构造器放进扩展后，结构体原有的成员构造器仍可在这里使用，调用方也能继续调用它。若把自定义构造器写进原始结构体声明，编译器是否合成成员构造器的规则会不同，因此移动代码不只是排版变化。

把 `CustomStringConvertible` 遵循放在单独扩展中，可以让类型的核心存储与格式化契约保持清楚。协议遵循仍然是类型身份的一部分，不会因为声明被放在另一段源码中就变成可选功能。

## 陷阱

### 用计算属性伪装存储

> **陷阱:** 生成代码有时会在扩展里写带初始值的属性，或用全局字典按对象标识保存“附加字段”。前者无法编译，后者会引入生命周期、同步和标识复用问题。

**修复方法：** 状态属于类型不变量时，把它放回原类型；无法修改原类型时，创建拥有该状态的包装类型。只有平台互操作已有明确所有权和清理契约时，才考虑专用的关联存储机制。

### 把同名方法当作覆盖

> **陷阱:** 类扩展中的新方法不能被子类覆盖，协议扩展的额外成员也不会因为具体类型声明同名方法而获得动态分派。调用结果可能随着变量的静态类型改变。

**修复方法：** 类层次需要覆盖时，在原类声明中提供可覆盖成员。协议需要多态选择时，把成员声明为协议要求，再用扩展提供默认实现；同时测试具体值和 `any Protocol` 值两条调用路径。

### 让约束覆盖面过大

> **陷阱:** 为使用一个相等比较的方法，就给整个泛型类型加上 `Element: Equatable`，会让不需要比较的功能也无法用于其他元素类型。

**修复方法：** 把约束放在最窄的扩展或成员上。无条件能力留在基础声明中，依赖哈希、排序或并发安全的能力分别放进自己的受约束扩展。

### 污染公共命名空间

> **陷阱:** 对 `String`、`Array` 等常用类型添加宽泛名称，可能与另一个模块或未来标准库版本的成员冲突。导入集合变化后，原本明确的调用甚至可能变得歧义。

**修复方法：** 公共库应优先扩展自己拥有的类型或协议。确需扩展外部类型时，使用能表达领域含义的名称，并把扩展的访问级别控制在最小范围；命名空间包装器适合成组的应用专用操作。

### 随意声明追溯遵循

> **陷阱:** 当前模块既不拥有类型也不拥有协议时，新增遵循会占用全局唯一的类型与协议组合。上游以后加入相同遵循，可能让不同模块对同一操作产生不一致假设。

**修复方法：** 优先定义包装类型，或定义当前模块拥有的窄协议。确实必须声明追溯遵循时，用 `@retroactive` 明确接受责任，记录语义与迁移计划，并在依赖升级时检查重复遵循。

### 移动扩展后假定语义不变

> **陷阱:** 把扩展整理到另一文件，可能使它无法访问原类型的 `private` 成员，也可能让依赖同文件规则的协议遵循自动合成失败。代码位置会影响可见性和编译器可生成的实现。

**修复方法：** 移动前先检查私有访问与合成遵循。优先把需要实现细节的扩展留在同一文件；确需跨文件时，显式提供实现，或只在确有模块级调用方时谨慎放宽访问级别。

<!-- deep -->

## 分派取决于声明位置与静态类型

具体类型的普通扩展成员在编译期参与重载解析。它们是类型的成员，但这不等于类虚方法：扩展不能替已有成员提供覆盖，也不能让自己新增的类成员成为子类覆盖点。需要继承多态时，设计入口必须位于类的原始声明中。

协议要求建立了另一条调用路径。遵循类型满足要求时，编译器为该遵循选择见证；要求的默认实现也可以成为见证。值被擦除为 `any Protocol` 后，调用仍能借助遵循信息到达所选见证，而不是只看协议扩展源码。

扩展额外成员没有对应的协议要求，也就没有让每个遵循类型填入实现的见证槽。接收者的静态类型只有协议时，编译器能保证存在的就是扩展实现。具体类型上的同名成员只是另一个候选，只有静态类型暴露它时才会被选择；这正是第三个示例中两个标签不同的原因。

泛型函数 `func emit<T: Reportable>(_ value: T)` 保留具体类型参数，但对只在协议扩展中声明的成员，函数体仍按其可见约束进行类型检查。不要把“泛型保留类型信息”简化成“所有同名成员都会动态分派”；成员是否为要求仍是关键契约。

静态分派（static dispatch）描述的是根据编译期信息选择调用目标。它不是自动更快的性能承诺，也不应成为遗漏协议要求的理由。除非有可复现的基准与优化配置，否则这里只讨论语义差异。

## 条件扩展与遵循

条件扩展通过同类型要求、协议约束或 `where` 子句限制成员集合。编译器只有在当前泛型环境能证明条件成立时才允许调用，因此失败通常表现为“当前上下文不存在该成员”，而不是运行时返回 `false`。

条件遵循把同样的思想应用到协议关系。例如，`Batch` 只有在 `Element: Equatable` 时才是 `Equatable`。这种关系可以组合：当数组元素本身因条件遵循而可比较时，更外层的数组也能满足相应约束。

条件成员与条件遵循不是运行时能力探测 API。若输入类型只能在运行时得知，应选择存在类型、枚举或显式类型擦除，并定义失败行为；不要期望 `where` 子句替代动态建模。

约束应表达实现真正使用的最小能力。为了实现去重，哈希集合通常需要 `Hashable`；线性扫描只需要 `Equatable`。选择约束时也在选择复杂度与顺序语义，不能只根据哪段声明更短。

同一个泛型类型不能通过重叠条件获得两个相互竞争的同协议遵循。遵循必须在所有可能实参上保持一致且可唯一确定。需要不同语义时，应使用不同包装类型，让类型名称承载差异。

## 模块所有权与演进

为自己拥有的类型遵循外部协议通常安全，因为只有类型的拥有者能发布该类型的规范遵循。为外部类型遵循自己拥有的协议也保留了协议语义的控制权。两者都不属于当前模块时，未来版本的任一拥有者都无法看到你已经占用了这对关系。

Swift 6 的追溯遵循警告把这个演进风险变得可见。`extension ExternalType: @retroactive ExternalProtocol` 会消除警告，但不会创建命名空间，也不会让遵循只在当前文件有效。它是责任声明，不是隔离机制。

包装类型能提供新的名义身份，因此可以安全拥有协议遵循、存储额外状态并定义独立的不变量。代价是调用边界需要显式包装与解包。对于公共库，这个小成本通常比全局遵循冲突更容易维护。

只添加成员、不添加外部协议遵循的扩展没有同一种运行时遵循冲突，但仍有源代码层面的命名风险。模块名会参与符号标识，却不能保证同时导入两个同名扩展成员时调用仍然明确。因此，公共 API 仍应选择领域化名称并保持表面积克制。

扩展中的构造器也受模块边界约束。扩展其他模块的结构体时，构造器必须先委托给定义模块提供的构造器，才能访问 `self`。类扩展只能添加便利构造器，并最终委托到指定构造器，以维护原类的初始化规则。

## 构造器与自动合成边界

同一文件中的结构体扩展可以调用可见的成员构造器。把自定义构造器写在扩展中，还能保留原声明获得的成员构造器；把同样的构造器移入原声明，可能抑制自动合成。调用方依赖成员构造器时，应把声明位置当作 API 设计的一部分。

扩展其他模块定义的结构体时，新构造器不能先逐个给外部类型的存储属性赋值。它必须先调用定义模块公开的构造器，之后才能使用 `self`。这条规则让定义模块继续控制初始化不变量与未来的存储布局。

类的规则更严格。指定构造器和析构器必须位于原类声明中，扩展只能添加便利构造器。便利构造器最终委托到指定构造器，因此扩展无法绕过父类初始化链或让实例处于部分初始化状态。

编译器合成协议遵循也关注声明位置。`Equatable`、`Hashable` 等可合成遵循的扩展应与原类型位于同一文件，编译器才能检查其存储并生成实现。跨文件整理代码后出现错误时，不应添加虚假的空实现来压制诊断，而应移动遵循或明确写出正确实现。

这些限制共同保护一个边界：扩展可以增加接口和遵循，但原类型仍控制存储、初始化与核心不变量。需要改变这三者之一时，应修改原类型或引入新包装类型，而不是继续堆叠扩展。

扩展的测试还应从真实客户端模块编译，而不只在声明模块内部运行。同一模块中可见的 `internal` 成员，会让内部测试错过外部调用方遇到的访问控制错误。

对于公共扩展，应建立一个只导入发布模块的最小测试目标。它能同时验证成员是否真正导出、条件约束是否出现在接口中，以及新增遵循是否给依赖方带来歧义。

源码组织可以变化，公开语义却必须稳定。移动扩展、调整导入或升级依赖后，重新编译这个客户端测试，比只检查声明文件更能暴露边界回归。

<!-- /deep -->

[检查点: swift/extensions](https://codewiki.com/zh/swift/extensions/#checkpoint)

## 延伸阅读

- [Swift 编程语言：扩展](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/extensions/)
- [Swift 编程语言：协议](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/)
- [Swift 编程语言：泛型](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/)
- [Swift Evolution SE-0143：条件遵循](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0143-conditional-conformances.md)
- [Swift Evolution SE-0364：追溯遵循警告](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0364-retroactive-conformance-warning.md)
- [Swift 6.3 发布说明](https://www.swift.org/blog/swift-6.3-released/)
