枚举与模式匹配
问题
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()
}