跳到主要内容

枚举与模式匹配

问题

Swift 的枚举有哪些高级用法?关联值、原始值有什么区别?模式匹配有哪些形式?

答案

基础枚举

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

var dir = Direction.north
dir = .south // 类型已知时可以省略类型名

原始值(Raw Value)

枚举的每个 case 可以关联一个编译时确定的值:

// String 原始值
enum API: String {
case users = "/api/users"
case posts = "/api/posts"
case comments = "/api/comments"
}
API.users.rawValue // "/api/users"

// Int 原始值(自动递增)
enum StatusCode: Int {
case ok = 200
case notFound = 404
case serverError = 500
}

// 从原始值创建(可能失败,返回 Optional)
let status = StatusCode(rawValue: 404) // Optional(.notFound)
let unknown = StatusCode(rawValue: 999) // nil

关联值(Associated Value)

每个 case 可以携带不同类型的附加数据:

enum NetworkResult {
case success(data: Data, statusCode: Int)
case failure(error: Error)
case loading(progress: Double)
}

let result = NetworkResult.success(data: Data(), statusCode: 200)

// 解构关联值
switch result {
case .success(let data, let statusCode):
print("Got \(data.count) bytes, status: \(statusCode)")
case .failure(let error):
print("Error: \(error)")
case .loading(let progress):
print("Loading: \(progress * 100)%")
}
原始值 vs 关联值
  • 原始值:编译时确定,所有 case 类型一致,通过 rawValue 访问
  • 关联值:运行时确定,每个 case 可以有不同类型,通过模式匹配提取

两者不能同时使用

枚举的方法与计算属性

enum Planet: Int, CaseIterable {
case mercury = 1, venus, earth, mars

var isHabitable: Bool {
self == .earth
}

func distanceFromSun() -> Double {
switch self {
case .mercury: return 0.39
case .venus: return 0.72
case .earth: return 1.0
case .mars: return 1.52
}
}

// mutating:可以修改自身
mutating func next() {
let allCases = Planet.allCases
guard let index = allCases.firstIndex(of: self),
index + 1 < allCases.count else { return }
self = allCases[index + 1]
}
}

递归枚举

indirect 标记包含自身的 case:

indirect enum ArithExpr {
case number(Int)
case addition(ArithExpr, ArithExpr)
case multiplication(ArithExpr, ArithExpr)
}

// (2 + 3) * 4
let expr = ArithExpr.multiplication(
.addition(.number(2), .number(3)),
.number(4)
)

func evaluate(_ expr: ArithExpr) -> Int {
switch expr {
case .number(let n): return n
case .addition(let a, let b): return evaluate(a) + evaluate(b)
case .multiplication(let a, let b): return evaluate(a) * evaluate(b)
}
}

evaluate(expr) // 20

模式匹配的六种形式

1. switch 匹配

let value: Any = 42

switch value {
case let x as Int where x > 0:
print("Positive Int: \(x)")
case is String:
print("It's a String")
case let (x, y) as (Int, Int):
print("Tuple: \(x), \(y)")
default:
print("Unknown")
}

2. if case / guard case

let result: NetworkResult = .success(data: Data(), statusCode: 200)

// if case:匹配特定 case
if case .success(let data, _) = result {
print("Data: \(data)")
}

// guard case:提前退出
guard case .success(_, let code) = result, code == 200 else {
return
}

3. for case(循环中的模式匹配)

let items: [Int?] = [1, nil, 3, nil, 5]

// 只遍历非 nil 的元素
for case let item? in items {
print(item) // 1, 3, 5
}

// 匹配特定 case
let responses: [NetworkResult] = [...]
for case .failure(let error) in responses {
print("Error: \(error)")
}

4. 元组匹配

let point = (x: 2, y: 0)

switch point {
case (0, 0):
print("Origin")
case (let x, 0):
print("On x-axis at \(x)")
case (0, let y):
print("On y-axis at \(y)")
case (let x, let y) where x == y:
print("On diagonal")
case (let x, let y):
print("(\(x), \(y))")
}

5. 范围匹配

let age = 25

switch age {
case 0..<13: print("Child")
case 13..<18: print("Teenager")
case 18..<65: print("Adult")
case 65...: print("Senior")
default: print("Invalid")
}

6. ~= 运算符(自定义模式匹配)

// 自定义:让正则表达式支持模式匹配
func ~= (pattern: String, value: String) -> Bool {
value.range(of: pattern, options: .regularExpression) != nil
}

let email = "test@example.com"
switch email {
case "[a-z]+@[a-z]+\\.[a-z]+":
print("Valid email format")
default:
print("Invalid")
}

枚举实战:Result 类型

Swift 标准库的 Result 就是一个枚举:

enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}

// 使用
func fetchUser(id: Int) -> Result<User, NetworkError> {
guard id > 0 else {
return .failure(.invalidId)
}
return .success(User(id: id, name: "Alice"))
}

// 模式匹配解构
switch fetchUser(id: 1) {
case .success(let user):
print(user.name)
case .failure(let error):
print(error)
}

常见面试问题

Q1: enum 的内存布局是什么样的?

答案

  • 无关联值:使用 1 个字节(如果 case 数 ≤ 256)存储 case 索引
  • 有关联值:大小 = 最大 case 的关联值大小 + tag 大小。所有 case 共享同一块内存
MemoryLayout<Direction>.size  // 1(4 个 case,1 字节足够)
MemoryLayout<Optional<Int>>.size // 9(8 字节 Int + 1 字节 tag)

Q2: enum 和 struct 都是值类型,有什么区别?

答案

  • enum 用于表示固定的几种可能状态,每个 case 可以携带不同关联值
  • struct 用于表示数据的聚合,所有属性始终存在

enum 不能有存储属性(只能有计算属性和方法),struct 可以。enum 更适合建模有限状态集。

Q3: CaseIterable 协议有什么用?

答案

遵循 CaseIterable 后,编译器自动生成包含所有 case 的数组:

enum Season: CaseIterable {
case spring, summer, autumn, winter
}
Season.allCases // [.spring, .summer, .autumn, .winter]
Season.allCases.count // 4

注意:带关联值的 enum 不能自动合成 CaseIterable

Q4: 什么是 @frozen@unknown default

答案

  • @frozen:标记枚举不会在未来版本中添加新 case(用于库的 ABI 稳定性)
  • @unknown default:匹配"未来可能新增的 case",同时编译器会在没有覆盖所有已知 case 时发出警告
switch someError {
case .notFound: ...
case .unauthorized: ...
@unknown default: // 处理未来可能新增的 case
handleUnknown()
}

相关链接