扩展 Extensions
本教程共 93 篇 · 第 72 篇 · 更新于 2026-08-08 · 约 9 分钟阅读
本节目标:学会用
extension给已有类型追加新功能(属性、方法、初始化器、下标、嵌套类型),还能让没有源码的类型去遵循协议。
有时候你拿到一个类型,想给它加点功能,却改不了它的原始定义——可能是标准库的类型,也可能是别人写的模块。Swift 的扩展(extension)就是干这个的:在不碰原代码的前提下,往已有类型里”续”上新能力。它有点像 Objective-C 的 category,但 Swift 的扩展没有名字。
1-1 扩展能做什么、不能做什么
扩展可以给类、结构体、枚举、协议追加这些能力:计算属性(实例或类型)、实例方法和类型方法、新的初始化器、下标、新的嵌套类型,以及让已有类型去遵循某个协议。
有一条铁律要记住:扩展不能覆盖已有功能,也不能加存储属性或给已有属性加观察器(willSet/didSet)。它只能”新增”,不能”改写”或”加存储”。这是为了避免行为冲突——同一个方法若能被多处改写,代码会乱套。
Note扩展追加的功能,对”在扩展定义之前就已经存在的实例”同样生效。类型一旦被扩展,所有实例立刻拥有新能力,不分新旧。
1-2 扩展语法
用 extension 关键字开头,后面跟要扩展的类型名:
extension SomeType {
// 新功能写这里
}
如果要顺带让这个类型遵循若干协议,把协议名写在冒号后,跟类、结构体的写法一致:
extension SomeType: SomeProtocol, AnotherProtocol {
// 协议要求的实现写这里
}
下面是一个常见用法:给 Swift 内置的 Double 加几个”单位换算”计算属性,方便用点语法把数字当长度用。
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
let threeFeet = 3.ft
let aMarathon = 42.km + 195.m
这些属性是只读计算属性,写法上省略了 get。1.m 表示”1 米”,42.km 表示”42 公里”,表达式读起来像在拼一句人话。它们本质是计算属性,返回值仍是 Double,所以能直接参与运算。
注意这里又一次印证了限制:扩展只能加”计算”属性,不能加”存储”属性。想给 Double 塞个存值的字段是做不到的。
1-3 用扩展加初始化器
扩展能给已有类型加新的初始化器。比如给一个 Rect 结构体加一个”用中心点 + 尺寸”来初始化的便利初始化器:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
新初始化器先根据中心点算好左上角 origin,再调用结构体自带的成员逐一初始化器 init(origin:size:) 完成赋值。对类来说,扩展只能加”便利初始化器”,不能加”指定初始化器”或”反初始化器”——指定初始化器和反初始化器必须由类本体的原始实现提供。
Warning扩展加的初始化器,你仍要自己保证”每个实例都被完全初始化”。别忘了调回原有的初始化器去填满所有存储属性,否则编译不过。
1-4 扩展加方法,含会改自身的 mutating
扩展能加实例方法和类型方法。下面给 Int 加一个 repetitions(task:),让整数表示”把任务重复执行几次”:
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("你好!")
}
// 打印三次"你好!"
task 的类型是 () -> Void,即一个无参数、无返回的函数。整数 3 直接点调用,把闭包体执行三遍。这种写法把”次数”和”动作”绑得很紧,读起来直观。
如果扩展加的方法要改动值类型自身(self),必须标 mutating,和原始实现里的规则一样。下面给 Int 加一个原地平方的方法:
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square() // someInt 变成 9
结构体和枚举的扩展方法若要修改 self 或其属性,漏写 mutating 会编译报错。这条要求跟在原始类型里写 mutating 方法完全一致。
1-5 扩展加下标
扩展能给已有类型加新下标。下面给 Int 加一个整数下标,返回”从右往左数第 n 位”的数字:
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0] // 5
746381295[1] // 9
746381295[2] // 2
123456789[0] 取最右位 9,[1] 取倒数第二位 8,依此类推。如果数字位数不够,下标实现返回 0,相当于左边补零后取值。下标让”按位取值”变成一行点语法式的自然操作。
1-6 扩展加嵌套类型
扩展还能往已有类型里加嵌套类型。下面给 Int 加一个嵌套枚举 Kind,表示整数是负、零还是正,并加一个 kind 计算属性返回对应情况:
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0: return .zero
case let x where x > 0: return .positive
default: return .negative
}
}
}
之后任意 Int 都能用 number.kind,在 switch 里直接用 .negative 这种简写(因为类型已知是 Int.Kind)。嵌套类型把”和某类型强相关的小类型”收进它该在的作用域里,代码更整洁。
1-7 让已有类型遵循协议
扩展最常见的用途之一,是让一个你没源码的类型去遵循某协议(这叫 retroactive modeling,回溯式建模)。比如第 67 章的 TextRepresentable 协议,标准库的 Array 本身就有 append、count 和 Int 下标,正好满足要求,于是只需一句空扩展:
extension Array: TextRepresentable {}
Swift 靠现有的方法自动推断出关联类型,Array 从此能当作 TextRepresentable 用。这种”不改原定义、用扩展补齐协议要求”的方式,是 Swift 组织代码的核心习惯:把协议遵循、把辅助能力都塞进扩展,让主定义保持干净。
1-8 扩展的代码组织价值
除了”补全功能”,扩展还有一个常被低估的用处:整理代码。很多 Swift 开发者喜欢把类型的主定义写得极简,只放存储属性和最关键的初始化器;把协议遵循、各种辅助方法分别塞进不同的扩展,每个扩展头顶标着它”负责哪块能力”。这样读代码时,想看”这个类型遵循了哪些协议”一目了然,想加新功能也知道该往哪个扩展里放。这种”按能力分扩展”的风格,让大类型不再是一整面密密麻麻的墙,而是被切成几块清爽的抽屉。
1-9 小结
扩展让你在不改动原定义的前提下,给类、结构体、枚举、协议追加计算属性、方法、初始化器、下标、嵌套类型,以及让已有类型去遵循协议。它不能覆盖已有功能、不能加存储属性。扩展追加的能力对全部既有实例立即生效。善用扩展,能把”主定义”和”附加能力 / 协议遵循”分离,代码既清晰又好维护。至此模块八里协议与扩展的部分讲完,下一章我们看访问控制。