为什么要有"协议扩展"
-
协议只能"声明"要求,不能"实现"要求
在 Swift 2 之前,协议类似 Java 的 Interface:
- 只能写方法签名,不能写大括号实现。
- 所有遵守者必须自己抄一遍模板代码,导致大量重复。
-
协议扩展的到来(Swift 2+)
官方给协议本身也增加了 extension 语法,允许:
- 提供默认实现(default implementation)
- 追加新的计算属性 / 方法 / 下标 / 关联类型约束
于是"协议 + 扩展"合二为一,既能约束,又能给代码。
-
与"基类"相比的优势
- 多继承效果:一个类型可同时遵守多条带默认实现的协议。
- 值类型可用:struct/enum 也能享受默认实现,而它们没有继承。
- Retroactive:即使第三方类型,只要让它遵守协议即可注入功能。
- 可拆分:把"接口"与"实现"彻底分离,各模块按需依赖。
语法速览
swift
protocol 某能力 {
func foo() // 声明:遵守者"可以"自己实现
}
extension 某能力 {
func foo() { print("默认 foo") } // 默认实现
}
调用优先级(谁赢?)
类型自身实现 > 协议扩展实现 > 父类实现
也就是说:
- 如果类型自己写了 foo,就用自己的;
- 如果没写,就用协议扩展的;
- 如果多个协议都提供了默认实现,则类型必须提供多个协议中相同方法的实现
4 大能力拆解
- 提供默认实现(最常用)
swift
protocol TextDescribable {
var text: String { get }
}
extension TextDescribable {
// 80% 的场景只需要这个实现
var text: String { String(describing: self) }
}
// 结构体什么都不用做就获得了 text
struct Point: TextDescribable {
let x, y: Double
}
let p = Point(x: 1, y: 2)
print(p.text) // "Point(x: 1.0, y: 2.0)"
- 追加新功能(协议自己"加私货")
swift
protocol CollectionPlus {}
extension CollectionPlus where Self: Collection {
/// 任何 Collection 一键转 JSON 数组字符串
func jsonString() -> String? {
guard
let data = try? JSONSerialization.data(withJSONObject: Array(self), options: [])
else { return nil }
return String(data: data, encoding: .utf8)
}
}
// 让数组自动获得能力
extension Array: CollectionPlus {}
print([1,2,3].jsonString()) // Optional("[1,2,3]")
要点:
where Self: Collection把能力限制在"真正遍历得到元素"的类型上,避免String等也误闯。- 这种"协议 + where"组合又称"条件扩展",是 POP 的精髓。
- 多协议冲突与消除歧义
swift
protocol A {
func play()
}
protocol B {
func play()
}
extension A {
func play() {
print("A")
}
}
extension B {
func play() {
print("B")
}
}
struct C: A, B {
// 有同名的方法,必须在这里实现
func play() {
print("C")
}
}
let c = C()
c.play()
// 这里不管怎样as,都会调用C中的实现
(c as A).play() // A
(c as B).play() // B
结论:
- 协议扩展不会"重载",只会"冲突"。
- 在设计 SDK 时,尽量给方法加前缀,或把能力拆成更细粒度协议。
- 扩展协议本身也能"关联类型"约束
swift
protocol Summable {
associatedtype Element
func reduce() -> Element
}
extension Summable where Element: Numeric {
// 只有 Element 遵守 Numeric 才给默认实现
func reduce() -> Element { 0 }
}
利用这套机制,我们可以把"算法"写成协议,再根据不同关联类型分发不同默认实现。
实战 3 段式:"接口 → 默认实现 → 具体类型"
场景:做一个日志组件,既能本地 print,也能远程上报,还能一键关闭。
- 定义接口
swift
protocol Logger {
func log(_ msg: String, file: String, line: Int)
}
- 提供默认实现(99% 模块只需要打印)
swift
extension Logger {
func log(_ msg: String,
file: String = #fileID,
line: Int = #line) {
#if DEBUG
print("🪵\(file):\(line) | \(msg)")
#endif
}
}
- 任意类型一键获得日志能力
swift
final class NetworkManager: Logger {} // 空实现即可
NetworkManager().log("请求接口 /user/info")
如果想"远程上报"怎么办?
------只在自己类型里重新实现 log(),默认实现就被覆盖,其他模块不受影响。
swift
class RemoteLog: Logger {
func log(_ msg: String, file: String = #file, line: Int = #line) {
print("假装这里上报到了远端")
}
}
RemoteLog().log("any message")
与泛型结合:写出"算法级别"的复用
例子:给一个"能比较"的数组追加"最大 N 个"方法
swift
protocol TopNable {}
extension TopNable where Self: Sequence, Self.Element: Comparable {
func topN(_ n: Int) -> [Self.Element] {
let sorted = self.sorted(by: >)
return Array(sorted.prefix(n))
}
}
extension Array: TopNable {}
print([3, 1, 7, 2, 9].topN(3)) // [9, 7, 3]
- 没有创建基类,也没有改动 Array 源码。
- 任何
Sequence+Comparable的类型都能一键获得topN能力。 - 想换算法?只改协议扩展一处即可。
协议扩展不能做的事
-
不能给协议新增"存储属性"
只能写计算属性,原因与类型扩展一样:无法分配内存。
-
不能给协议写
deinit/ 存储型观察器协议本身没有生命周期。
-
不能"强制"让遵守者失去默认实现
一旦提供了默认实现,遵守者永远"隐式"拥有它;想强制其重写,只能把默认实现删掉。
-
不能防止"菱形冲突"
多协议默认实现冲突时,必须重新实现冲突的方法
工作流 checklist:如何设计一套"POP 组件"
- 先写最小协议,只放"必须"约束。
- 再写协议扩展,给通用能力提供默认实现。
- 用
where做条件扩展,把算法拆成"高内聚"的小协议。 - 给可能冲突的方法加前缀,或拆成更细粒度协议。
- 值类型优先(struct/enum),避免继承树。
- 单元测试:针对"协议 + 默认实现"写测试,任何遵守者自动受益。
- 文档:在协议头上写注释,Xcode 会自动带到所有遵守者,无需重复写。
总结
- 协议扩展 = 协议(约束) + 扩展(实现),是 Swift 对"接口编程"的终极回答。
- 它让"多继承"在值类型世界成为可能,同时保持静态派度、无运行时开销。
- 掌握"默认实现 + where 约束"两件套,就能把"算法"从类型身上剥离出来,真正做到"写一次,到处复用"。
- 牢记冲突规则与优先级,设计 SDK 时留好"消除歧义"的逃生舱。