Swift: Swift 枚举与模式匹配教程:从基础关联值到递归枚举

枚举在 Swift 中远不止"一组常量"——它是一等公民,可以携带数据、支持方法,甚至递归定义自身,是构建状态机和结果类型的最佳工具。

1. 你将学到


2. 一个移动端开发者的真实故事

(1) 痛点:API 响应状态难以表达

Alice 在开发一个新闻 App 的网络层时,遇到了一个棘手问题。API 请求可能返回多种结果:

SWIFT
// 不优雅的做法:用多个可选变量表示状态
var data: NewsResponse? = nil
var error: Error? = nil
var isLoading = false

这种方式有三个问题:data 和 error 同时存在时含义模糊忘记处理某个状态导致崩溃代码逻辑分散难以维护

(2) 枚举关联值的解法

用一个枚举统一表达所有状态,每个 case 携带自己的数据:

SWIFT
enum NetworkResult<T> {
    case success(T)
    case failure(Error)
    case loading
}

NetworkResult<[NewsArticle]> 要么携带文章数据(success),要么携带错误信息(failure),要么是 loading 状态——三种状态互斥,语义清晰

(3) 收益:状态管理从混乱到清晰

维度 多可选变量方案 枚举关联值方案
状态数量 2^N 种非法组合 N 种精确状态
语义清晰度 ❌ data/error 可能共存 ✅ 每种状态自带数据
处理完整性 ⚠️ 容易漏处理 ✅ switch 穷举检查
代码可维护性 分散在 if-else 中 ✅ 集中在一个类型

3. 枚举基础

枚举定义一组相关的值,Swift 的枚举比其他语言的更灵活——可以关联方法、计算属性和下标。

(1) 基本枚举与原始值

100%
graph LR
    A["enum Compass {\n case north\n case south\n case east\n case west\n}"] --> B["Compass.north"]
    A --> C["Compass.south"]
    A --> D["Compass.east"]
    A --> E["Compass.west"]

原始值(Raw Value)是枚举 case 背后的固定值,可以是 String、Int 等类型。

原始值类型 声明方式
Int enum Status: Int { case ok = 200 } 整数
String enum Direction: String { case north = "N" } 字符串
隐式 Int enum Grade: Int { case a, b, c } 从 0 开始递增
隐式 String enum Code: String { case red, green } 与 case 名相同

▶ 示例:HTTP 状态码枚举

SWIFT
// ============================================
// 使用原始值的 HTTP 状态码枚举
// ============================================
enum HTTPStatus: Int {
    case ok = 200
    case created = 201
    case badRequest = 400
    case unauthorized = 401
    case notFound = 404
    case serverError = 500
}
let status = HTTPStatus.notFound
print("Code: \(status.rawValue)")
print("Name: \(status)")
// 从原始值反向创建
let parsed = HTTPStatus(rawValue: 200)
print("Parsed: \(parsed ?? .serverError)")

输出:

TEXT 📖 仅展示
Code: 404
Name: notFound
Parsed: ok

(2) 枚举方法与计算属性

枚举可以像结构体一样定义方法和属性。

▶ 示例:带方法的枚举

SWIFT
// ============================================
// 枚举自定义方法:判断状态类别
// ============================================
enum HTTPStatus: Int {
    case ok = 200
    case created = 201
    case badRequest = 400
    case unauthorized = 401
    case notFound = 404
    case serverError = 500
    var isSuccess: Bool {
        rawValue >= 200 && rawValue < 300
    }
    var description: String {
        switch self {
        case .ok: return "OK"
        case .created: return "Created"
        case .badRequest: return "Bad Request"
        case .unauthorized: return "Unauthorized"
        case .notFound: return "Not Found"
        case .serverError: return "Internal Server Error"
        }
    }
}
let code = HTTPStatus.ok
print("\(code.rawValue) \(code.description) - Success: \(code.isSuccess)")

输出:

TEXT 📖 仅展示
200 OK - Success: true

4. 关联值与模式匹配

关联值是枚举区别于其他语言的核心特性——每个 case 可以携带不同类型的附加数据。

(1) 关联值语法

100%
graph TB
    A["NetworkResult<T>"] --> B["success(T) - 携带数据"]
    A --> C["failure(Error) - 携带错误"]
    A --> D["loading - 无数据"]
    B --> E["switch result {\n case .success(let data):\n   show(data)\n case .failure(let error):\n   show(error)\n case .loading:\n   showSpinner()\n}"]
特性 原始值 关联值
值类型 固定常量 可变数据
存储位置 定义时确定 创建时传入
每个 case 都有相同类型的值 每个 case 可以不同
使用场景 映射固定编码 携带动态数据

▶ 示例:网络请求结果处理

SWIFT
// ============================================
// 关联值枚举处理 API 响应
// ============================================
enum NetworkResult<T> {
    case success(T)
    case failure(String)
    case loading
}
func handleResponse<T>(_ result: NetworkResult<T>) {
    switch result {
    case .success(let data):
        print("成功获取数据: \(data)")
    case .failure(let error):
        print("请求失败: \(error)")
    case .loading:
        print("加载中...")
    }
}
handleResponse(NetworkResult.success("用户信息"))
handleResponse(NetworkResult.failure("网络连接超时"))
handleResponse(NetworkResult.loading)

输出:

TEXT 📖 仅展示
成功获取数据: 用户信息
请求失败: 网络连接超时
加载中...

(2) if-case 与 guard-case 模式匹配

除了 switch,还可以用 if caseguard case 进行单 case 匹配。

▶ 示例:简洁的单 case 匹配

SWIFT
// ============================================
// if-case 和 guard-case 的简洁用法
// ============================================
enum Measurement {
    case weight(Double)
    case height(Double)
    case count(Int)
}
let record = Measurement.weight(75.5)
// if-case 只关心一种情况
if case .weight(let kg) = record, kg > 70 {
    print("体重大于 70kg: \(kg)")
}
// guard-case 提前退出
func process(_ m: Measurement) {
    guard case .count(let n) = m else {
        print("不是计数类型")
        return
    }
    print("计数为 \(n)")
}
process(.weight(65))
process(.count(42))

输出:

TEXT 📖 仅展示
体重大于 70kg: 75.5
不是计数类型
计数为 42

5. 递归枚举

递归枚举指枚举的关联值又引用了自身,适合表达树状或嵌套结构。

(1) indirect 关键字

indirect 标记递归 case 或整个枚举,告知编译器需要间接存储。

写法 说明
indirect case expression(Expression) 单个 case 递归
indirect enum Expression { ... } 整个枚举允许递归

▶ 示例:数学表达式求值

SWIFT
// ============================================
// 递归枚举:数学表达式树
// ============================================
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
func evaluate(_ expr: ArithmeticExpression) -> Int {
    switch expr {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}
// 构建表达式 (3 + 5) * 2
let three = ArithmeticExpression.number(3)
let five = ArithmeticExpression.number(5)
let sum = ArithmeticExpression.addition(three, five)
let product = ArithmeticExpression.multiplication(sum, .number(2))
print("(3 + 5) * 2 = \(evaluate(product))")

输出:

TEXT 📖 仅展示
(3 + 5) * 2 = 16

6. 完整示例:订单状态机

SWIFT
// ============================================
// 完整示例:电商订单状态机
// 功能:订单状态流转 + 数据处理
// ============================================
import Foundation
// 1. 订单状态枚举(关联值)
enum OrderStatus {
    case pending          // 待支付
    case paid(Date)       // 已支付(含支付时间)
    case shipped(trackingNumber: String)  // 已发货(含运单号)
    case delivered(Date)  // 已送达(含签收时间)
    case cancelled(reason: String)  // 已取消(含原因)
    var description: String {
        switch self {
        case .pending: return "待支付"
        case .paid(let date): return "已支付 (\(date.formatted()))"
        case .shipped(let tracking): return "已发货 (运单: \(tracking))"
        case .delivered(let date): return "已送达 (\(date.formatted()))"
        case .cancelled(let reason): return "已取消: \(reason)"
        }
    }
    var isFinalState: Bool {
        if case .delivered = self { return true }
        if case .cancelled = self { return true }
        return false
    }
}
// 2. 使用示例
let now = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let statuses: [OrderStatus] = [
    .pending,
    .paid(now),
    .shipped(trackingNumber: "SF1234567890"),
    .delivered(now),
    .cancelled(reason: "用户取消")
]
for status in statuses {
    let final = status.isFinalState ? "(终态)" : ""
    print("\(status.description) \(final)")
}

输出:

TEXT 📖 仅展示
待支付
已支付 (2026-07-30)
已发货 (运单: SF1234567890)
已送达 (2026-07-30) (终态)
已取消: 用户取消 (终态)

❓ 常见问题

Q 枚举和结构体如何选择?
A 枚举适合"一组互斥状态"——某个时间点只能是其中一种;结构体适合"多个属性共存"——所有属性同时存在。选哪个取决于你的数据模型是"或"关系还是"与"关系。
Q 关联值和泛型能不能一起用?
A 可以。enum Optional<T> 就是关联值 + 泛型的经典例子:case nonecase some(T)。关联值可以引用泛型参数。
Q 为什么 Swift 枚举比 C/Java 的 enum 强大?
A Swift 枚举可以有方法、计算属性、关联值、递归定义、遵守协议。C/Java 的 enum 只是命名的整数常量,Swift 枚举是完整的一等类型。
Q 什么时候必须用 indirect?
A 当枚举的关联值类型包含自身时。例如 ArithmeticExpression.addition 的关联值是 ArithmeticExpression,形成递归引用,必须 indirect。
Q switch 匹配枚举时一定要写 default 吗?
A 推荐穷举所有 case,不要用 default。这样未来新增 case 时,编译器会警告遗漏,避免漏处理。但如果确实不需要处理所有 case,可以加 default。

📖 小节


📝 作业

  1. 基础题: 定义一个 TrafficLight 枚举,包含 red、yellow、green 三个 case,并添加一个 duration 计算属性返回每种灯光的持续秒数。
  2. 进阶题: 使用关联值定义一个 Either<L, R> 枚举(类似 Rust 的 Result),包含 left(L)right(R) 两个 case,并添加 map 方法转换 right 值。
  3. 挑战题: 用递归枚举实现一个 JSON 类型(支持 null、bool、int、string、array、dictionary),并编写一个函数将 JSON 枚举格式化为缩进字符串。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏