Swift 协议(Protocol)指南(四):协议扩展(Protocol Extension)——让“协议”自己也有默认实现

为什么要有"协议扩展"

  1. 协议只能"声明"要求,不能"实现"要求

    在 Swift 2 之前,协议类似 Java 的 Interface:

    • 只能写方法签名,不能写大括号实现。
    • 所有遵守者必须自己抄一遍模板代码,导致大量重复。
  2. 协议扩展的到来(Swift 2+)

    官方给协议本身也增加了 extension 语法,允许:

    • 提供默认实现(default implementation)
    • 追加新的计算属性 / 方法 / 下标 / 关联类型约束

    于是"协议 + 扩展"合二为一,既能约束,又能给代码。

  3. 与"基类"相比的优势

    • 多继承效果:一个类型可同时遵守多条带默认实现的协议。
    • 值类型可用:struct/enum 也能享受默认实现,而它们没有继承。
    • Retroactive:即使第三方类型,只要让它遵守协议即可注入功能。
    • 可拆分:把"接口"与"实现"彻底分离,各模块按需依赖。

语法速览

swift 复制代码
protocol 某能力 {
    func foo()          // 声明:遵守者"可以"自己实现
}
extension 某能力 {
    func foo() { print("默认 foo") }  // 默认实现
}

调用优先级(谁赢?)

类型自身实现 > 协议扩展实现 > 父类实现

也就是说:

  • 如果类型自己写了 foo,就用自己的;
  • 如果没写,就用协议扩展的;
  • 如果多个协议都提供了默认实现,则类型必须提供多个协议中相同方法的实现

4 大能力拆解

  1. 提供默认实现(最常用)
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)"
  1. 追加新功能(协议自己"加私货")
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 的精髓。
  1. 多协议冲突与消除歧义
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 时,尽量给方法加前缀,或把能力拆成更细粒度协议。
  1. 扩展协议本身也能"关联类型"约束
swift 复制代码
protocol Summable {
    associatedtype Element
    func reduce() -> Element
}

extension Summable where Element: Numeric {
    // 只有 Element 遵守 Numeric 才给默认实现
    func reduce() -> Element { 0 }
}

利用这套机制,我们可以把"算法"写成协议,再根据不同关联类型分发不同默认实现。

实战 3 段式:"接口 → 默认实现 → 具体类型"

场景:做一个日志组件,既能本地 print,也能远程上报,还能一键关闭。

  1. 定义接口
swift 复制代码
protocol Logger {
    func log(_ msg: String, file: String, line: Int)
}
  1. 提供默认实现(99% 模块只需要打印)
swift 复制代码
extension Logger {
    func log(_ msg: String,
             file: String = #fileID,
             line: Int = #line) {
        #if DEBUG
        print("🪵\(file):\(line) | \(msg)")
        #endif
    }
}
  1. 任意类型一键获得日志能力
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 能力。
  • 想换算法?只改协议扩展一处即可。

协议扩展不能做的事

  1. 不能给协议新增"存储属性"

    只能写计算属性,原因与类型扩展一样:无法分配内存。

  2. 不能给协议写 deinit / 存储型观察器

    协议本身没有生命周期。

  3. 不能"强制"让遵守者失去默认实现

    一旦提供了默认实现,遵守者永远"隐式"拥有它;想强制其重写,只能把默认实现删掉。

  4. 不能防止"菱形冲突"

    多协议默认实现冲突时,必须重新实现冲突的方法

工作流 checklist:如何设计一套"POP 组件"

  1. 先写最小协议,只放"必须"约束。
  2. 再写协议扩展,给通用能力提供默认实现。
  3. where 做条件扩展,把算法拆成"高内聚"的小协议。
  4. 给可能冲突的方法加前缀,或拆成更细粒度协议。
  5. 值类型优先(struct/enum),避免继承树。
  6. 单元测试:针对"协议 + 默认实现"写测试,任何遵守者自动受益。
  7. 文档:在协议头上写注释,Xcode 会自动带到所有遵守者,无需重复写。

总结

  • 协议扩展 = 协议(约束) + 扩展(实现),是 Swift 对"接口编程"的终极回答。
  • 它让"多继承"在值类型世界成为可能,同时保持静态派度、无运行时开销。
  • 掌握"默认实现 + where 约束"两件套,就能把"算法"从类型身上剥离出来,真正做到"写一次,到处复用"。
  • 牢记冲突规则与优先级,设计 SDK 时留好"消除歧义"的逃生舱。
相关推荐
HarderCoder3 小时前
Swift 协议(Protocol)指南(三):Primary Associated Type、some/any 与泛型式协议实战
swift
HarderCoder3 小时前
Swift 协议(Protocol)指南(二):关联类型、Self 约束与泛型递归,一次彻底搞懂
swift
HarderCoder3 小时前
Swift 协议(Protocol)指南(一):从语法到实战
swift
HarderCoder3 小时前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区11 小时前
iOS 基于 Foundation Model 构建媒体流
ios·iphone·swift·媒体
大熊猫侯佩21 小时前
侠客行・iOS 26 Liquid Glass TabBar 破阵记
ios·swiftui·swift
qixingchao2 天前
iOS SwiftUI 动画开发指南
ios·swiftui·swift
大熊猫侯佩2 天前
猿族代码战记:Mutex 升级版——守护 Swift 并发的“香蕉仓库”
swiftui·swift·apple
大熊猫侯佩2 天前
Thread.sleep 与 Task.sleep 终极对决:Swift 并发世界的 “魔法休眠术” 揭秘
ios·swift·apple