本篇目标

掌握 Swift 枚举的定义、关联值与原始值,理解协议的声明、遵循与扩展,学会用协议实现面向协议编程。

一、枚举基础

枚举用于定义一组相关的值。

enum Direction {
    case north
    case south
    case east
    case west
}

let dir = Direction.north

简化写法:

enum Direction {
    case north, south, east, west
}

使用枚举:

var direction = Direction.north

switch direction {
case .north:
    print("向北")
case .south:
    print("向南")
case .east:
    print("向东")
case .west:
    print("向西")
}

特点:

  • 枚举是值类型。

  • 枚举的每个分支都是确定的值。

  • switch 匹配枚举必须穷举,或加 default。

二、原始值

枚举可以有一个原始值,类型可以是 String、Int、Double、Character 等。

enum Weekday: Int {
    case monday = 1
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}

print(Weekday.monday.rawValue)  // 1
print(Weekday.friday.rawValue)  // 5

自动递增:

enum Status: Int {
    case pending  // 0
    case running  // 1
    case done     // 2
}

字符串原始值:

enum Environment: String {
    case dev = "development"
    case prod = "production"
}

print(Environment.dev.rawValue)  // development

用原始值初始化:

if let env = Environment(rawValue: "development") {
    print(env)  // dev
}

注意:

  • 原始值初始化返回可选类型。

  • 原始值不能重复。

  • 有原始值的枚举,不能有关联值。

三、关联值

枚举的每个分支可以关联不同类型和数量的值。

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

let barcode1 = Barcode.upc(8, 85909, 51226, 3)
let barcode2 = Barcode.qrCode("ABCDEFGHIJKLMNOP")

使用关联值:

switch barcode1 {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case .qrCode(let code):
    print("QR: \(code)")
}

简写(所有关联值都用 let):

switch barcode1 {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case let .qrCode(code):
    print("QR: \(code)")
}

应用场景:

  • 网络请求结果:成功(数据)或失败(错误)

  • 状态机:加载中、成功、失败

  • 表达式:数字、字符串、变量

四、枚举的属性和方法

枚举可以定义计算属性和方法。

enum CompassPoint {
    case north, south, east, west

    var description: String {
        switch self {
        case .north: return "北"
        case .south: return "南"
        case .east: return "东"
        case .west: return "西"
        }
    }
}

print(CompassPoint.north.description)  // 北

实例方法:

enum TrafficLight {
    case red, yellow, green

    func canGo() -> Bool {
        return self == .green
    }
}

print(TrafficLight.green.canGo())  // true

mutating 方法:

enum Switch {
    case off, on

    mutating func toggle() {
        self = self == .off ? .on : .off
    }
}

var s = Switch.off
s.toggle()
print(s)  // on

五、CaseIterable

让枚举可以遍历所有分支。

enum Direction: CaseIterable {
    case north, south, east, west
}

for dir in Direction.allCases {
    print(dir)
}

数量:

print(Direction.allCases.count)  // 4

六、协议基础

协议定义一组方法和属性的要求,遵循协议的类、结构体、枚举必须实现。

protocol Greetable {
    var name: String { get }
    func greet()
}

struct Person: Greetable {
    var name: String

    func greet() {
        print("Hello, \(name)")
    }
}

let p = Person(name: "Tom")
p.greet()  // Hello, Tom

属性要求:

  • { get }:只读,可实现为只读或读写。

  • { get set }:读写,必须可读可写。

protocol Named {
    var name: String { get set }
}

方法要求:

protocol Calculable {
    func add(a: Int, b: Int) -> Int
}

mutating 方法要求:

protocol Resettable {
    mutating func reset()
}

struct Counter: Resettable {
    var count = 0

    mutating func reset() {
        count = 0
    }
}

七、协议作为类型

协议可以作为类型使用,存储任何遵循该协议的值。

protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    var radius: Double

    func area() -> Double {
        return .pi * radius * radius
    }
}

struct Rectangle: Shape {
    var width: Double
    var height: Double

    func area() -> Double {
        return width * height
    }
}

let shapes: [Shape] = [
    Circle(radius: 1),
    Rectangle(width: 2, height: 3)
]

for shape in shapes {
    print(shape.area())
}

用途:

  • 数组元素类型统一

  • 函数参数类型

  • 属性类型

八、协议继承

协议可以继承其他协议。

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

protocol Person: Named, Aged {
    func introduce()
}

struct Student: Person {
    var name: String
    var age: Int

    func introduce() {
        print("我是 \(name),今年 \(age) 岁")
    }
}

注意:

  • 遵循子协议时,需要实现所有父协议的要求。

  • 一个类型可以同时遵循多个协议。

九、协议扩展

协议可以通过扩展提供默认实现。

protocol Greetable {
    var name: String { get }
    func greet()
}

extension Greetable {
    func greet() {
        print("Hello, \(name)")
    }
}

struct Person: Greetable {
    var name: String
}

let p = Person(name: "Tom")
p.greet()  // Hello, Tom

优点:

  • 减少重复代码。

  • 让协议拥有默认行为。

为协议添加约束:

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != first {
                return false
            }
        }
        return true
    }
}

print([1, 1, 1].allEqual())  // true
print([1, 2, 1].allEqual())  // false

十、协议组合

使用 & 组合多个协议。

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

func printInfo(person: Named & Aged) {
    print("\(person.name),\(person.age) 岁")
}

用途:

  • 函数参数要求同时满足多个协议。

  • 类型别名简化复杂组合。

typealias PersonInfo = Named & Aged

十一、可选协议要求

用 @objc 标记的协议可以定义可选要求,仅适用于类。

@objc protocol DataSource {
    func numberOfItems() -> Int
    @objc optional func titleForItem(at index: Int) -> String
}

class MyDataSource: DataSource {
    func numberOfItems() -> Int {
        return 10
    }
}

注意:

  • 可选要求只能用于 @objc 协议。

  • 遵循类型必须是类。

  • 纯 Swift 场景建议用协议扩展提供默认实现,替代可选要求。

十二、面向协议编程

Swift 提倡面向协议编程(POP),优先用协议 + 组合,而不是继承。

示例:

protocol Flyable {
    func fly()
}

protocol Swimmable {
    func swim()
}

struct Duck: Flyable, Swimmable {
    func fly() {
        print("鸭子在飞")
    }

    func swim() {
        print("鸭子在游泳")
    }
}

struct Fish: Swimmable {
    func swim() {
        print("鱼在游泳")
    }
}

优点:

  • 灵活组合能力。

  • 避免类的单继承限制。

  • 便于测试和替换实现。

十三、避坑指南

  • 枚举分支尽量少而清晰:过多分支考虑拆分。

  • 关联值 vs 原始值:不能同时存在。

  • 协议命名规范:表示能力用 -able、-ible,表示身份用名词。

  • 协议扩展默认实现:注意不要与具体实现冲突。

  • @objc 可选要求:能不用就不用,优先用协议扩展。

  • 协议组合:类型太多时用 typealias 简化。

十四、检查清单

  • [ ] 能定义枚举,使用原始值

  • [ ] 理解关联值的用法

  • [ ] 会为枚举添加计算属性和方法

  • [ ] 会使用 CaseIterable

  • [ ] 能定义协议,遵循协议

  • [ ] 理解协议作为类型

  • [ ] 会使用协议扩展提供默认实现

  • [ ] 了解面向协议编程的思路