基础语法:一份"合同"长什么样
swift
// 1. 定义协议:只声明,不实现
protocol FullyNamed {
// 只要可读,不要求可写
var fullName: String { get }
}
// 2. 遵守协议:编译器会强制兑现
struct Person: FullyNamed {
// 存储属性即可满足
var fullName: String
}
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
// 计算属性同样可以满足协议
var fullName: String {
(prefix != nil ? prefix! + " " : "") + name
}
}
知识点小结
- 协议不分存储/计算属性,只关心"名字+类型+读写权限"。
- 协议不能规定默认值、不能要求存储/计算属性二选一。
方法需求与变体方法(mutating)
swift
protocol Togglable {
// 允许修改值类型自身
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case off, on
// enum 必须标记 mutating
mutating func toggle() {
self = self == .off ? .on : .off
}
}
var s = OnOffSwitch.off
s.toggle() // on
Tips
- class 实现 mutating 方法时不需要写
mutating(引用类型默认可变)。 - 协议里的
init需求,在 class 实现时必须写required,保证子类也能履约。
协议里的初始化器 & 可失败初始化器
swift
protocol SomeProtocol {
init(param: Int) // 非可失败
init?(failParam: Double) // 可失败
}
class MyClass: SomeProtocol {
required init(param: Int) {} // 必须写 required
required init?(failParam: Double) {} // 可失败版本也要 required
}
协议作为类型:面向接口编程
swift
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
print(thing.textualDescription) // 统一调用,无需关心真实类型
}
三种"协议类型"场景
- 泛型约束
<T: SomeProtocol> - 不透明类型
some SomeProtocol(编译期确定,调用方看不到具体类型) - 装箱协议类型(Existential)
any SomeProtocol(运行时决定,有微小性能开销)
委托(Delegation)模式实战
swift
// 1. 定义协议(嵌套在类内部)
class DiceGame {
weak var delegate: Delegate? // 弱引用防循环引用
protocol Delegate: AnyObject { // 类专属协议
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didEndRound: Int, winner: Int?)
func gameDidEnd(_ game: DiceGame)
}
func play(rounds: Int) {
(0..<rounds).forEach { i in
delegate?.gameDidStart(self)
delegate?.game(self, didEndRound: i, winner: i)
delegate?.gameDidEnd(self)
}
}
}
// 2. 任何类都能当"记录员"
class DiceGameTracker: DiceGame.Delegate {
func gameDidStart(_ game: DiceGame) { print("开始新游戏") }
func game(_ game: DiceGame, didEndRound r: Int, winner: Int?) {
print("第\(r)轮 winner=\(winner ?? 0)")
}
func gameDidEnd(_ game: DiceGame) { print("游戏结束") }
}
// 3. 运行
let game = DiceGame()
let gameTracker = DiceGameTracker()
game.delegate = gameTracker
game.play(rounds: 3)
协议扩展:给协议"写实现"
swift
protocol RandomNumberGenerator {}
extension RandomNumberGenerator {
// 所有遵守者自动获得
func randomBool() -> Bool {
random() > 0.5
}
}
// 默认实现 + where 约束
extension Collection where Element: Equatable {
func allEqual() -> Bool {
guard let first = self.first else { return true }
return self.allSatisfy { $0 == first }
}
}
好处
- 避免重复代码;
- 允许"协议+约束"泛型算法;
- 提供默认实现后,类型可再自定义覆盖。
协议继承与协议组合
swift
protocol TextRepresentable {}
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
// 继承TextRepresentable
protocol PrettyTextRepresentable: TextRepresentable {
var pretty: String { get }
}
func birthday(person: Named & Aged) { // 同时遵守两个协议
print("Happy \(person.age), \(person.name)!")
}
可选协议需求(仅兼容 Objective-C)
swift
@objc protocol CounterDataSource {
@objc optional func increment(forCount: Int) -> Int
@objc optional var fixedInc: Int { get }
}
- 必须
@objc & class才能用; - 调用时用可选链
dataSource?.increment?(forCount: 5)。
隐式合成与条件一致性
swift
protocol TextRepresentable {
var textualDescription: String { get }
}
// 1. 编译器自动帮你实现 Equatable / Hashable / Comparable
struct Point3D: Equatable { // 不自己写 == 也行
var x, y, z: Double
}
// 2. 条件一致性:给泛型类型"按需"加协议
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
"[" + map(\.textualDescription).joined(separator: ", ") + "]"
}
}
检查与转换协议类型
swift
for object in objects {
if let areaObj = object as? HasArea {
print("面积=\(areaObj.area)")
} else {
print("没有面积属性")
}
}
实战技巧 & 常见坑
- 性能:
- Existential(装箱协议)有微小间接调用开销,热点代码可改泛型
<T: Protocol>或some Protocol。
- Existential(装箱协议)有微小间接调用开销,热点代码可改泛型
- 关联类型:
- 带
associatedtype的协议只能当泛型约束,不能直接当变量类型(后续文章展开)。
- 带
- 循环引用:
- 委托一定用
weak+ 类专属协议: AnyObject。
- 委托一定用
- 协议粒度:
- 小功能拆小协议,再
&组合,比"胖协议"更灵活。
- 小功能拆小协议,再