Swift 的枚举设计,比较能体现这门语言的设计哲学:
让类型系统表达程序的真实意图,用编译器保证安全,用枚举表达可能性空间
更加清晰和细致的类型系统,是现代编程语言的发展趋势,swift 中 enum 被视为一等公民。足见其重要性,很多 Swift 语言的基础设施都是通过 enum 来提炼类型系统实现的。
1、例如 swift 中的 Optional 的实现就是个枚举:
enum Optional<Wrapped> { case none case some(Wrapped) }
这个泛型枚举,可以看做为一个类型集合:
{ none, some(关联值 wrapped) },有了这个类型集合,编译期就可以对每个编译期的值进行类型推断和检测。这就是让编译期用类型系统保证安全。2、还有 Result 也是通过枚举实现的
enum Result<Success, Failure: Error> { case success(Success) case failure(Failure) }
可见 Swift 中的 enum 很重要,可以用枚举实现很多基础实现。
Enum 分类
为了方便理解和记忆,swift 的 enum 设计可以分为 3 类:
- 状态集合枚举 (标签枚举、无原始值枚举), Tag Enum
- 有原始值的枚举 RawValue Enum
- 关联值枚举 Associated Enum;带标签的联合体(tagged union),一种变体 variant。
其中 rawValue Enum 和 associated Enum 属于不同的设计范畴,一个枚举不能同时拥有原始值和关联值。
1、Tag Enum
就是最普通的枚举,没有原始值,仅定义一组有限的状态,编译期决定好类型:
enum Direction { case up, down, left, right }
在底层其实是一个整型 tag (猜测):
// 伪代码 struct Direction { enum Tag { up, down, left, right } Tag _tag; }
这种枚举可以类比为 javascript 中的 Symbol 类型。
2、RawValue Enum
这种是值映射型,相当于对 C 语言等整型枚举的一个扩展。
enum Direction: String { case up = "U" case down = "D" }
底层编译期生成伪代码:
struct Direction { enum Tag { up, down } Tag _tag; static let rawValues = ["U", "D"] }
这种枚举可以做更灵活的语义化表达,某些场景用来提升代码可读性。RawValue 枚举的另一个好处,可以通过一个 rawValue 初始化一个枚举,不过初始化的是一个可选
Optional 类型的值enum TestCaseAgency { case DevTest case ProdTest } let t1: TestCaseRaw? = TestCaseRaw.init(rawValue: "DevTest"); let t2: TestCaseRaw? = TestCaseRaw.init(rawValue: "emp"); if let r1 = t1 { print("r1: ", r1.rawValue); } if let _ = t2 { } else { print("empty t2"); }
3、Associated Enum
关联值,目的是可以额外绑定数据,让某种状态下的表现能力更强。例如系统的 Result:
enum Result<Success, Failure> where Failure: Error { case success(Success) case failure(Error) }
结果从枚举层面看,可以分为两种状态:成功、失败。但实际情况是,成功状态下还需要处理对应的数据,这个数据就是这种状态下的关联值。这样既能使用枚举做类型限定,同时还能表达数据。
枚举的底层实现类似 “带标签的联合体”:
enum Direction { case move(x: Int, y: Int) case stop }
底层模型(伪 C++):
struct Direction { enum Tag { move, stop } _tag; union { struct { int x, y; } moveData; }; }
总结到这里,看看 swift 官方文档的描述:
“Enumerations like these are also known as discriminated unions, tagged unions, or variants.”
此外需要注意,关联值枚举,是个运行时类型,绑定的值需要运行时才能确认。
4、使用场景总结
类型 | 场景 | 示例 |
基本枚举 | 固定状态集合 | enum Direction { case up, down } |
原始值枚举 | 映射外部值(API、持久化、枚举名序列化) | enum HTTPMethod: String { case get="GET", post="POST" } |
关联值枚举 | 表达变体(variant)类型或结果类型 | enum Result { case success(Data), failure(Error) } |
这三种写法其实体现了 Swift 的三个设计理念:
枚举类型 | 设计哲学 | 关键词 |
基本枚举 | 用类型表示状态空间 | 安全、显式 |
原始值枚举 | 类型安全的值映射 | 序列化、桥接外部系统 |
关联值枚举 | 代数数据类型(ADT) | 表达力、可组合性 |
获取原始值和关联值
1、获取原始值
Enum 有原生支持的属性 rawValue
enum TestCaseRaw: String { case DevTest = "dev" case ProdTest = "prod" } let case1 = TestCaseRaw.DevTest print(case1.rawValue) // 输出: "dev"
- .rawValue 是 Swift 自动生成的只读属性;
- 只能用于定义了原始值类型(enum Name: Type {})的枚举;
- 原始值在编译期固定(编译器直接分配表)。
2、获取关联值
2.1 使用
switch 模式匹配,let 解包来获取值switch case2 { case .DevTest(let a, let b): print("DevTest 参数:", a, b) case .ProdTest(let str): print("ProdTest 参数:", str) }
2.2 使用
if case 解构(适合单一判断)if case let .ProdTest(env) = case3 { print("当前环境:", env) }
值类型的 enum 属性支持
1、先明确,swift 的 enum 是值类型,值拷贝
无论 enum 内部放的是什么,enum 本身是值类型。这个含义是,enum 赋值的时候,都是值拷贝。这是由语言层级(编译器定义)决定的,不会因为它内部“引用”了类实例而改变。
enum Container { case number(Int) case object(MyClass) } class MyClass { var value: Int init(_ v: Int) { self.value = v } } var a = Container.object(MyClass(10)) var b = a // 值拷贝(enum 是值类型)
此时:
- a 和 b 是两个独立的 枚举值
- 但它们内部都“引用了”同一个 MyClass 实例
也就是说:
enum 的复制是“浅拷贝”语义上的值拷贝。但是如果其中携带了引用类型,那么内部的引用不会被复制。
这与结构体的行为是一致的。
2、enum 内部支持的属性定义
2.1 不支持存储属性
因为 enum 是一种状态表示的集合,支持存储属性,会破坏状态的稳定性。枚举的本质是一个 有限离散状态的联合类型(sum type),每个 case 可能携带不同关联值。如果允许存储属性,就会破坏这个模型的“固定内存布局”特性,使得每个 case 的内存结构不再可预测。
enum TestEnum { var name: String = "Hello" // ❌ 编译错误 }
2.2 支持计算属性和类型属性
计算属性:通过计算返回,不占用额外的存储空间
enum Direction { case north, south, east, west var opposite: Direction { switch self { case .north: return .south case .south: return .north case .east: return .west case .west: return .east } } var description: String { switch self { case .north: return "↑ North" case .south: return "↓ South" case .east: return "→ East" case .west: return "← West" } } } let d = Direction.east print(d.opposite) // west print(d.description) // → East
静态属性用于枚举级别的共享信息,不依赖具体枚举实例。
enum APIEnvironment { case dev, staging, prod static let defaultBaseURL = "https://api.example.com" var baseURL: String { switch self { case .dev: return "https://dev.api.example.com" case .staging: return "https://staging.api.example.com" case .prod: return APIEnvironment.defaultBaseURL } } } print(APIEnvironment.defaultBaseURL) print(APIEnvironment.prod.baseURL)