Swift 协议

协议(protocol)是 Swift 抽象的核心。它类似 Java 的接口、Rust 的 trait,但更强大——支持默认实现、关联类型、面向协议编程。Apple 把它提升到了"代码复用主要手段"的高度。本章讲透。

1. 协议是什么

协议定义"能做什么"的契约,而不关心"是谁"。任何类型(struct/class/enum)都可以采纳协议:

// 协议:定义"能做什么"的契约(类似接口)
protocol Describable {
    var description: String { get }       // 只读属性
    func describe() -> String             // 方法签名
}

protocol Drawable {
    func draw()
}

// 任何类型(类、结构体、枚举)都可以采纳协议
struct Point: Describable, Drawable {     // 多个协议用逗号
    var x: Double
    var y: Double

    var description: String {
        return "(\(x), \(y))"
    }
    func describe() -> String {
        return "Point at \(description)"
    }
    func draw() {
        print("绘制点 \(description)")
    }
}

let p = Point(x: 1, y: 2)
print(p.describe())              // Point at (1.0, 2.0)

Swift 协议的几个特点:

2. 协议扩展:默认实现(核心特性)

这是 Swift 协议区别于 Java 接口的最大特性。你可以给协议提供默认实现,也可以加新方法:

// 协议扩展:给协议提供"默认实现"
protocol Greeter {
    func greet() -> String
}

extension Greeter {
    // 默认实现
    func greet() -> String {
        return "你好"
    }
    // 可以加新方法(协议本身没声明)
    func greetLoudly() -> String {
        return greet().uppercased()
    }
}

// 采纳协议,自动获得 greet() 和 greetLoudly()
struct Chinese: Greeter {}
let c = Chinese()
print(c.greet())                 // 你好(默认实现)
print(c.greetLoudly())           // 你好(自动获得)

// 也可以覆盖默认实现
struct English: Greeter {
    func greet() -> String { return "Hello" }
}
print(English().greet())         // Hello

这条机制让协议变成"代码复用"的强大工具——可以基于协议构建大型功能,实现类型获得"免费的"方法。

3. 面向协议编程(POP)

Apple 在 2015 年 WWDC 提出"面向协议编程"(Protocol-Oriented Programming),主张用协议 + 扩展替代传统的类继承。看一个例子:

// 面向协议编程(POP):用协议做类型,而非具体类
protocol Animal {
    var name: String { get }
    func sound() -> String
}

extension Animal {
    func speak() -> String {
        return "\(name): \(sound())"
    }
}

struct Dog: Animal { let name: String; func sound() -> String { "汪汪" } }
struct Cat: Animal { let name: String; func sound() -> String { "喵喵" } }
struct Cow: Animal { let name: String; func sound() -> String { "哞哞" } }

// 用协议当类型,数组里放不同类型
let zoo: [Animal] = [
    Dog(name: "旺财"), Cat(name: "咪咪"), Cow(name: "牛牛"),
]
for animal in zoo {
    print(animal.speak())        // 自动调各自的 sound()
}

// 限制:协议类型只能访问协议声明的方法
// 如果 Dog 有独有方法 fetch(),通过 Animal 类型访问不到

POP 的核心思想:

Swift 标准库本身就是 POP 的典范——大量协议(Array 是 Sequence,Int 是 Numeric,String 是 Collection)支撑整个标准库。

4. 标准库常用协议

Swift 标准库提供了大量"协议 + 默认实现"组合,让你的类型实现一个协议,免费获得一整套功能:

// Swift 标准库大量使用协议,常用如下:

// 1. CustomStringConvertible:print 时的自定义描述
struct User: CustomStringConvertible {
    let name: String
    var description: String { return "User(\(name))" }
}
print(User(name: "小明"))         // User(小明)

// 2. Equatable:判等(自动合成)
struct Vec2: Equatable { let x, y: Double }
print(Vec2(x: 1, y: 2) == Vec2(x: 1, y: 2))   // true

// 3. Hashable:可哈希(自动合成)
struct Tag: Hashable { let name: String }
let tags: Set<Tag> = [Tag(name: "swift"), Tag(name: "ios")]

// 4. Codable:JSON 自动编解码
import Foundation
struct Profile: Codable { let name: String; let age: Int }
let json = """{"name":"小明","age":18}""".data(using: .utf8)!
let profile = try! JSONDecoder().decode(Profile.self, from: json)
print(profile.name)              // 小明

最常见的四个:

5. 关联类型:泛型协议

Swift 协议支持 associatedtype(关联类型),让协议可以"占位"具体类型。这相当于协议的泛型:

// 关联类型:协议里的"占位类型"(泛型协议)
protocol Container {
    associatedtype Item          // 由实现者决定具体类型
    var count: Int { get }
    func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

// 实现:用 typealias 显式(也可推断)
struct IntStack: Container {
    typealias Item = Int         // 可省略,从下面推断
    private var items: [Int] = []
    var count: Int { items.count }
    func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}

// 泛型实现
struct Stack<Element>: Container {
    private var items: [Element] = []
    var count: Int { items.count }
    func append(_ item: Element) { items.append(item) }
    subscript(i: Int) -> Element { items[i] }
}

// 注意:有关联类型的协议不能直接当类型(只能当泛型约束)
// func first<C: Container>(_ c: C) -> C.Item? { ... }

关联类型让协议变得非常灵活。比如标准库的 Collection 协议就有关联类型 Element,Array、Dictionary、Set 都采纳它,但元素类型不同。注意:有关联类型的协议不能直接当类型(需用泛型约束)。

6. 协议 vs 继承

场景协议类继承
多个类型共享行为✓ 多采纳✗ 单继承
struct/enum 复用
共享状态/存储✗ 协议不能有存储属性
构造链

实战建议:默认用协议,只有需要"共享存储 + 构造链"才用类继承。

7. 常见陷阱

小结

Swift 协议是抽象的核心。掌握:声明 + 采纳 + 协议扩展默认实现 + 关联类型 + 标准库协议。Apple 推荐"先协议,后实现",写出解耦、可测试、可复用的代码。下一篇讲闭包——和协议一起构成 Swift 抽象的"双子星"。

← 上一篇 Swift 类与结构体

下一篇 Swift 闭包

✈️💬