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 协议的几个特点:
- 隐式实现:不像 Java 的
implements关键字,Swift 类型采纳协议直接在类型名后写: Protocol,实现协议要求的方法即可。 - 可采纳多个:用逗号分隔,如
: A, B, C。 - struct/enum/class 都能采纳——不像 Java 接口只能给 class。
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 的核心思想:
- 用协议当类型:
[Animal]数组里可以放 Dog/Cat/Cow,享受多态。 - 协议扩展提供共享逻辑:speak() 的默认实现在协议扩展里,所有采纳者都免费获得。
- 不强制继承层次:struct 可以采纳协议,不需要共享父类。
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) // 小明最常见的四个:
- Equatable:能用
==比较。 - Hashable:能作 Set/Dictionary 的元素。
- Comparable:能比较大小,可排序。
- CustomStringConvertible:print 时显示自定义文字。
- Codable:JSON 自动编解码(实际是 Encodable + Decodable)。
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. 常见陷阱
- 协议不能有存储属性:只能要求"有这个属性",不能存值。具体类型负责存。
- 存在类型(Any*):有关联类型的协议做"异构数组"需要
any Protocol包装(Swift 5.7+),或用类型擦除。 - 循环采纳:协议 A 采纳 B,B 又采纳 A,编译错误。
- 未实现:声明采纳协议但漏实现方法,编译错误(除非协议有默认实现)。
小结
Swift 协议是抽象的核心。掌握:声明 + 采纳 + 协议扩展默认实现 + 关联类型 + 标准库协议。Apple 推荐"先协议,后实现",写出解耦、可测试、可复用的代码。下一篇讲闭包——和协议一起构成 Swift 抽象的"双子星"。
← 上一篇 Swift 类与结构体
下一篇 Swift 闭包 →