Swift 的协议:比接口更灵活
如果你写过 Java 或 C# 的接口,Swift 的 protocol 是个更灵活的版本。它不只是"规定这个类要实现哪些方法",而是一种"能力标签":任何类型(结构体、类、枚举)只要声明遵守某个协议,就等于贴上了"我能做这件事"的标签。
Swift 的标准库几乎全是用协议搭起来的:Array、Set、Dictionary 都遵守 Collection 协议,因此对它们写的代码几乎一样。这让 Swift 既能写得安全(编译期检查),又能写得灵活(通用算法复用)。
Swift 官方推崇"协议导向编程":先想清楚你的类型有哪些能力(协议),再让具体类型去实现它们,而不是先把类层次搭好。
- protocol定义一组方法、属性的要求,不写实现。
- 遵守协议在类型名后面加 : 协议名,再补全要求。
- 协议扩展用 extension 给协议加默认实现。
- 泛型函数用 <T> 让一份逻辑适用于多种类型。
- associatedtype协议内部声明一个占位类型。
定义协议:规定"能做什么"
用 protocol 关键字定义一组要求。下面这个 HasArea 协议规定:任何遵守它的类型,都得有一个可读的 area 属性。
protocol HasArea {
var area: Double { get } // 只要能读就行
}
protocol Named {
var name: String { get }
}
注意协议里只写"要有什么",不写"具体怎么算"。这是协议和普通类的区别:它是一份契约,不是一份实现。
让类型遵守协议
让具体类型在名字后面加 : HasArea,再把协议要求的东西补上。下面给 Circle 和 Rect 两个结构体都加上 HasArea。
struct Circle: HasArea {
let radius: Double
var area: Double {
return Double.pi * radius * radius
}
}
struct Rect: HasArea {
let w: Double, h: Double
var area: Double {
return w * h
}
}
现在 Circle 和 Rect 都"是"HasArea 了——你可以写一个函数,只要求参数是 HasArea,不关心它具体是圆还是方。
协议扩展:给协议加默认实现
Swift 有个特别好用的特性:可以用 extension 直接给协议本身加方法。这样所有遵守协议的类型,自动就白拿到这个方法,不用每个类型都写一遍。
extension HasArea {
func isBigger(than other: HasArea) -> Bool {
return self.area > other.area
}
}
let c = Circle(radius: 2)
let r = Rect(w: 3, h: 3)
print(c.isBigger(than: r)) // true
Circle 和 Rect 都没自己写 isBigger,却自动能用——因为它们都遵守 HasArea。这就是协议导向编程省代码的地方。
泛型:写一次、到处用
和 Go、Rust 一样,Swift 也有泛型。写一个对任意类型数组找第一个元素的函数:
func firstElement<T>(of array: [T]) -> T? {
return array.first
}
let nums = firstElement(of: [1, 2, 3]) // Int?
let words = firstElement(of: ["a", "b"]) // String?
<T> 是占位类型,编译器根据实参推断具体是什么。还可以加约束:"T 必须遵守 Equatable",这样函数里就能用 == 比较。
associatedtype:协议里的关联类型
有时候协议里需要一个"类型占位"——比如一个 Container 协议,它装的元素类型是什么,由遵守者自己决定。这就是 associatedtype。
protocol Container {
associatedtype Item // 占位:具体是什么类型由遵守者定
var count: Int { get }
mutating func append(_ item: Item)
}
struct IntStack: Container {
typealias Item = Int // 这个栈装的是 Int
var items: [Int] = []
var count: Int { items.count }
mutating func append(_ item: Int) {
items.append(item)
}
}
Swift 标准库的 Sequence、Collection 都是这样设计的——这也是为什么 Array、Set 能被同一套算法处理。
实战:一个可打印的协议栈
把协议、协议扩展、泛型串起来:定义一个 Container 协议,给它加一个"打印内容"的默认方法,再写一个泛型栈去遵守它。
protocol Stackable {
associatedtype Element
mutating func push(_ e: Element)
mutating func pop() -> Element?
}
extension Stackable {
func describe() -> String {
return "这是一个装 \(Element.self) 的栈"
}
}
struct MyStack<T>: Stackable {
var items: [T] = []
mutating func push(_ e: T) { items.append(e) }
mutating func pop() -> T? { items.popLast() }
}
var s = MyStack<String>()
s.push("hello")
s.push("world")
print(s.describe()) // 这是一个装 String 的栈
print(s.pop()!) // world
栈的核心逻辑写一次,describe 这种通用能力通过协议扩展自动送给所有遵守者。这就是 Swift 在 Apple 平台上能把 UI 组件、数据模型都搭得干净的原因。
协议 + 泛型是 Swift 的灵魂:协议规定能力、泛型复用逻辑、协议扩展白送默认实现——三者一合,代码又短又安全。