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、使用场景总结
Unsupported Notion block: table
这三种写法其实体现了 Swift 的三个设计理念:
Unsupported Notion block: table
获取原始值和关联值
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)