什么是 Extension
-
定义
extension 是 Swift 提供的一种纵向扩展机制:"不修改原始代码、不创建子类"的前提下,给任意类型(class / struct / enum / protocol)追加功能。
-
与 OC Category 的区别
- OC Category 需要名字,Swift 扩展无名字。
- OC Category 能"声明"属性但不能"实现"存储属性;Swift 扩展同样只能写计算属性,但编译期直接报错而非运行时崩溃。
- Swift 扩展支持协议遵守、泛型 where 约束,OC 做不到。
语法模板
swift
extension 已有类型 [:协议1, 协议2] {
// 新增功能
}
注意:
- 扩展体里不能写
stored property(存储属性)。 - 扩展体里不能给类新增
deinit或designated init。 - 扩展会全局生效,只要模块被 import,功能就可见;因此务必把"只内部用"的扩展标记为
internal或private。
7 大能力逐一拆解
- 计算属性(只读 & 读写)
swift
extension Double {
// 以下都是"计算型属性",底层无存储,每次实时计算
var m: Double { self } // 米
var km: Double { self * 1_000.0 } // 千米 → 米
var ft: Double { self / 3.28084 } // 英尺 → 米
var cm: Double { self / 100.0 } // 厘米 → 米
}
// 用法:链式调用、参与运算
let runWay = 3.5.km + 200.m // 3 700 米
print("跑道长度:\(runWay)m")
常见坑:
- 写
set时必须同时提供get - 计算属性如果算法复杂,考虑用方法替代,避免"看起来像属性却耗时"的歧义。
- 方法(实例 & 类型)
swift
extension Int {
/// 将当前数值作为次数,重复执行无参闭包
func repetitions(task: () -> Void) {
for _ in 0..<self { task() }
}
}
3.repetitions {
print("Hello extension")
}
可变方法(mutating)
扩展里修改值类型自身时,必须加 mutating:
swift
extension Int {
mutating func square() {
self = self * self
}
}
var num = 9
num.square() // 81
- 便利构造器(convenience init)
规则:
- 只能给类加
convenience init; - 必须横向调用同类中的
designated init; - 值类型(struct/enum)扩展可写任意
init,只要"所有属性有默认值"或"最终横向调到原 init"。
swift
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)
}
}
let rect = Rect(center: Point(x: 5, y: 5), size: Size(width: 20, height: 10))
- 下标(Subscript)
swift
extension Int {
subscript(digitIndex: Int) -> Int {
// 位数不足左侧补 0
var decimal = 1
for _ in 0..<digitIndex { decimal *= 10 }
return (self / decimal) % 10
}
}
123456789[0] // 9
123456789[3] // 6
- 嵌套类型
swift
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
}
}
}
// 使用
let nums = [-3, 0, 5]
for n in nums {
print(n.kind)
}
- 协议遵守(Retroactive Modeling)
场景:第三方库定义了 User,你需要让 User 支持 Codable,但源码不可改。
做法:写扩展即可。
swift
// 假设 User 是别人的类型
struct User { let name: String }
// 我让它直接支持 Codable
extension User: Codable { }
// 现在可以
let data = try JSONEncoder().encode(User(name: "Kim"))
- 扩展泛型 + where 约束
swift
extension Array where Element == Int {
/// 仅当数组元素是 Int 时可用
func sum() -> Int { reduce(0, +) }
}
[1, 2, 3].sum() // 6
["a", "b"].sum() // 编译错误,方法不可见
扩展不能做的事
- 不能写
stored property(不会分配内存)。 - 不能给类新增
deinit/designated init。 - 不能覆盖(override)已有方法------但可以重载(overload)或使用协议默认实现"屏蔽"。
- 扩展中声明的私有属性/方法,作用域遵循 Swift 访问级别规则;跨模块扩展时,默认无法访问
internal成员,除非使用@testable。
实践总结
-
命名与作用域
- 扩展文件统一命名
类型+功能.swift,例如Double+Distance.swift。 - 只在本文件使用的扩展,用
private extension包起来,避免"全局污染"。
- 扩展文件统一命名
-
计算属性 vs 方法
- 无副作用、O(1) 返回,用属性;
- 有 IO、算法复杂、可能抛异常,用方法。
-
协议优先
如果功能具备"通用性",先定义协议,再用扩展提供默认实现,例如:
swiftprotocol ReusableView: AnyObject { static var reuseID: String { get } } extension ReusableView { static var reuseID: String { String(describing: self) } } // 所有 UITableViewCell 一键获得 reuseID -
避免"上帝扩展"
一个文件里动辄几百行的扩展,后期维护成本极高。按"能力维度"拆文件:
UIView+Shadow.swiftUIView+Gradient.swiftUIView+Snapshot.swift
可落地的 3 个业务场景
-
路由参数解析
给
URL扩展计算属性,快速取 query 值:swiftextension URL { var queryParameters: [String: String] { guard let q = query else { return [:] } return q.split(separator: "&").reduce(into: [:]) { result, pair in let kv = pair.split(separator: "=", maxSplits: 1) result[String(kv[0])] = kv.count > 1 ? String(kv[1]) : "" } } } -
错误日志统一
给
Error扩展log()方法,一键上报:swiftextension Error { func log(file: String = #file, line: Int = #line) { let msg = "\(Self.self) in \(file.split(separator: "/").last ?? ""):\(line) → \(localizedDescription)" Logger.shared.error(msg) } } -
商城 SKU 模型
后端返回的
SKU结构体缺少"是否缺货"字段,用扩展追加计算属性,避免改原始模型:swiftextension SKU { var isOutOfStock: Bool { stock <= 0 } }
结语
扩展是 Swift "开闭原则"的最佳注脚:
-
对修改封闭(不动源码),对扩展开放(任意追加)。
-
用好扩展,可以让主类型保持简洁、让功能按"维度"聚类、让团队协作不打架。
但切记:
-
"能力越大,责任越大"------不加节制地全局扩展,会让调用链难以追踪、命名冲突概率增大。
-
先想清楚"这是共性能力还是业务补丁",再决定"用扩展、用包装器、用继承还是用组合"。