Swift 协议(Protocol)指南(一):从语法到实战

基础语法:一份"合同"长什么样

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)   // 统一调用,无需关心真实类型
}

三种"协议类型"场景

  1. 泛型约束 <T: SomeProtocol>
  2. 不透明类型 some SomeProtocol(编译期确定,调用方看不到具体类型)
  3. 装箱协议类型(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("没有面积属性")
    }
}

实战技巧 & 常见坑

  1. 性能:
    • Existential(装箱协议)有微小间接调用开销,热点代码可改泛型 <T: Protocol>some Protocol
  2. 关联类型:
    • associatedtype 的协议只能当泛型约束,不能直接当变量类型(后续文章展开)。
  3. 循环引用:
    • 委托一定用 weak + 类专属协议 : AnyObject
  4. 协议粒度:
    • 小功能拆小协议,再 & 组合,比"胖协议"更灵活。
相关推荐
HarderCoder2 小时前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区10 小时前
iOS 基于 Foundation Model 构建媒体流
ios·iphone·swift·媒体
大熊猫侯佩20 小时前
侠客行・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
大熊猫侯佩2 天前
【大话码游之 Observation 传说】下集:破咒终局了,天眼定乾坤
ios·swift·apple
大熊猫侯佩2 天前
【大话码游之 Observation 传说】中集:仙流暗涌,计数迷踪现
ios·swift·apple