本节,分析枚举enum
- 各语言枚举区别
- swift枚举的使用
- swift枚举大小
- 枚举的嵌套
- 枚举的递归(indirect)
- OC桥接
- SIL分析
1. 各语言枚举区别
1.1 C语言枚举
- 仅支持
Int类型,默认首元素值为0,后续元素值依次+1。
如果中间有元素赋值,以赋值为准,后续没赋值的元素值依旧依次+1
enum WEEK {
Mon, Tue = 10, Wed, Thu, Fri, Sat, Sun
};
enum WEEK a = Mon;
enum WEEK b = Wed;
printf("%d",a); // 打印0
printf("%d",b); // 打印11
OC语言的枚举类型与C语言一致
1.2 Swift枚举
十分强大
- 格式:
不用逗号分隔,类型需使用case声明
- 内容:
- 支持
Int、Double、String等基础类型,有默认枚举值
(String类型默认枚举值为key名,Int、Double数值型默认枚举值为0开始+1递增 )- 支持
自定义选项
不指定支持类型,没有rawValue。但同样支持case枚举,可自定义关联内容。
- 指定类型:
没指定枚举值时,各类型都有默认枚举值。
// Double类型
// CaseIterable协议,有allCases属性,支持遍历所有case
enum Week1: Double, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week1.allCases.forEach { print($0.rawValue)}
// String类型
enum Week2: String, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week2.allCases.forEach { print($0.rawValue)}

- 自定义类型(
强大)
不指定枚举类型,可给枚举项添加拓展内容。switch读取时,可提取出拓展类型,进行相应操作。
// 自定义类型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
func printValue(_ v: Shape) {
// switch区分case(不想每个case处理,可使用default)
switch v {
case .square(let width):
print(width)
case .circle(let radius, _):
print(radius)
}
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
printValue(s)
printValue(c)

2. swift枚举的使用
- swift枚举的读取,有两种方式:
1.
统一使用switch区分case
- 判断
单类case,直接使用if语句
2.1 switch方式
- 灵活的
属性读取:
let声明整个case分开声明case中的每个关联内容合并case,使用同一个变量x
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
func printValue1(_ v: Shape) {
switch v {
// 1. let声明整个case
case let .square(x):
print(x)
// 2. 分开声明case中的每个关联内容
case .circle(let x, var y):
y += 100 // var声明的变量,可修改和赋值
print("radius: \(x), borderWidth: \(y)")
}
}
func printValue2(_ v: Shape) {
switch v {
// 3. 合并case,使用同一个变量x
case let .square(x), let .circle(x, _):
print(x)
}
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
print("------")
printValue1(s)
printValue1(c)
print("------")
printValue2(s)
printValue2(c)

2.2 if 方式
- 判断是否为
指定case项,并获取关联内容。
(同样支持整体case关联内容声明和分开声明)
// 自定义类型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
// 判断s是否是square类型。并获取`关联内容`
// 1. 内部声明关联内容类型(如: let)
if case .square(let width) = s {
print(width)
}
// 2. 声明case所有关联内容类型(如: var)
if case var .circle(radius, borderWidth) = c {
radius += 200
borderWidth += 100
print(radius, borderWidth)
}

2.3 计算型属性 & 函数
-
enum枚举支持计算型属性和函数
enum Direct: Int {
case up
case down
case left
case right
// 计算型属性
var description: String{
switch self {
case .up:
return "这是上面"
default:
return "这是\(self)"
}
}
// 函数
func printSelf() {
print(description)
}
}
Direct.down.printSelf() // 打印: 这是down
3. swift枚举大小
size:实际占用内存大小
stride:系统分配的内存大小
指定类型:
- 仅
一个case项:size为0(高版本xcode可能为1),stride为1)多个case项:
case小于255个:size为1,stride为1
超过255个会自动扩容,size和stride都会增加。
(原因,1字节(8bit)可区分255种情况。所以默认size为1,当只有一个case时,0x0即可表示。所以size为0和1都可理解)image.png
自定义:
占用内存空间最大的case大小 +enum自身大小:
image.png
如果不清楚Foo2的case大小size为何为18,可查看内存对齐
顺带提供一个
struct(5个属性值)大小计算方式:
MyStruct1 内存计算
4. 枚举的嵌套
-
枚举的嵌套,本质上只是在不同作用域内创建,并没有造成结构上的嵌套。
4.1 enum嵌套enum
enum Foo {
enum Direct: Int {
case up
case down
case left
case right
}
case leftUp(element1: Direct, element2: Direct)
case rightDown(element1: Direct, element2: Direct)
}
var f = Foo.leftUp(element1: .left, element2: .up)
4.2 struct嵌套enum
struct Foo {
enum Direct: Int {
case up
case down
case left
case right
}
let key: Direct
func printKey() {
switch key {
case .up: print("上")
case .down: print("下")
default:
print("其他")
}
}
}
var f = Foo(key: .down)
f.printKey() // 打印: 下
5. 枚举的递归(indirect)
- 枚举中
case关联内容使用自己枚举类型,是否会造成递归?枚举的大小如何确定?
案例:
树节点,需要重复使用:
image.png直接使用,XCode会报错。
(因为直接使用,enum的大小需要case来确定,而case的大小又需要使用到enum大小。所以无法计算大小,报错)
-
swift提供了indirect关键字,可以标记递归枚举,也可以标记单个case,被标记后,case项直接去堆中申请内存,变为引用类型,大小为指针8字节。
image.png
- 输出
SIL中间代码,可以看到是使用alloc_box创建枚举值,内部调用了swift_allocObject,去堆中申请空间:
image.png在SIL官方文档中,有介绍
alloc_box:
image.png从
汇编层也可佐证:
image.png
6.OC桥接
-
OC枚举仅支持Int类型,而swift支持多种类型。
6.1 OC使用swift枚举:
swift中创建Int类型枚举值,使用@objc声明
image.png
@objc声明后,桥接文件中,自动生成了OC的SWIFT_ENUM:
image.png
OC文件中,导入swift桥接头文件,直接调用SwiftEnum
image.png
6.2 swift使用OC枚举:
OC的.h头文件声明枚举类型:
typedef NS_ENUM(NSUInteger, OCEnum):自动转换成swift枚举typedef enum:转换成结构体
image.png
桥接文件中,添加OC头文件:
image.png
自动生成的swift文件中,可以看到转换的类型:
image.png
image.png
swift文件中使用:
NS_ENUM生成:可正常使用typedef enum生成:只能通过通过值初始化,再使用
image.png
6.3 OC使用swift枚举:
- 如果
swift中不是Int类型,而又希望OC能用,只能自己做个桥接。
(例如: 原本
swift中枚举类型为String,可直接通过rawValue读取值。
为了兼容OC,把类型改成Int,自定义计算型属性,禁止使用默认的rawValue读取)
swift中创建int类型的枚举,自定义string计算属性,并禁止rawValue的使用。
image.png
OC中直接使用:
image.png
- 如果还希望
OC能访问到swift对应的String值:
使用
class的类方法兼容:
class需要继承NSObject,类函数完成枚举与String的映射。enum禁止rawValue,改用string计算属性获取:
image.png
桥接文件中可以看到生成了OC的类方法和枚举:
image.png
image.png
OC文件中使用:
image.png
7. SIL分析
7.1 enum格式
- 案例代码:
enum Week: String {
case Mon
case Tue
case Wed
case Thu
case Fri
case Sat
case Sun
}
- 打开终端,
cd到当前文件夹,swiftc -emit-sil main.swift > ./main.sil命令输出SIL文件:
取消
swift函数名的混淆输出:swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil

7.2 rawValue的读取
- 在
SIL文件中,搜索rawValue的getter方法:
(switch跳转指定case,执行函数,得到case内容,返回case内容)
image.png
有2个疑问:
-
默认属性(字符串)是什么时候创建的? - 如何记录
case名和对应值的?
默认属性(字符串)是什么时候创建的?
编译期就会生成所有符号
image.png
- 所以上面
rawValue读取时,可直接通过string_literal从MachO中读取字符。
- 如何记录
case名和对应值的?
String类型的枚举,case与rawValue值(打印结果一样,但类型是对应枚举类型和String)- 通过
rawValue初始化case时,类型为Option(找不到对应case时,为nil)enum Week: String { case Mon case Tue case Wed case Thu case Fri case Sat case Sun } // case与rawValue值(打印结果一样,但类型不同) print("类型:\(type(of: Week.Mon)) 值:\(Week.Mon)") // 打印: 类型:Week 值:Mon print("类型:\(type(of: Week.Mon.rawValue)) 值:\(Week.Mon.rawValue)") // 打印: 类型:String 值:Mon // 通过rawValue来生成对应的case(可选类型,找不到rawValue对应的case,就是nil) print(Week.init(rawValue: "Mon")) // 打印: Optional(Demo.Week.Mon) print(Week.init(rawValue: "Hello")) // 打印: nil
通过
SIL分析init(rawValue:):
image.png
完整流程1.创建:
创建枚举(格式:元组(Array<T>,Pointer),此例中T为String) ,再遍历创建所有枚举项。2.查询:
通过_findStringSwitchCase获取入参值的index,使用switch通过index(int类型)匹配到case,匹配成功:返回optional的some值,匹配失败,直接返回nil在
swift源码中搜索findStringSwitchCase,可以看到是通过for遍历匹配index:
image.png
enum枚举较为简单,我们了解了与其他语言的差异、用法,顺带探索大小和源码实现。
- 下一节,介绍swift
闭包。

























