本节,分析枚举
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
都可理解)
自定义:
占用内存空间最大
的case
大小 +enum
自身大小:
如果不清楚Foo2
的case
大小size
为何为18
,可查看内存对齐
顺带提供一个
struct(5个属性值)
大小计算方式
:
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关联内容
使用自己枚举类型
,是否会造成递归
?枚举
的大小
如何确定?
案例:
树节点
,需要重复使用:
直接使用
,XCode
会报错
。
(因为直接使用
,enum
的大小
需要case
来确定
,而case
的大小
又需要使用到enum大小
。所以无法计算大小
,报错)
-
swift
提供了indirect
关键字,可以标记递归枚举
,也可以标记单个case
,被标记后,case项
直接去堆中申请内存
,变为引用类型
,大小为指针8字节
。
- 输出
SIL中间代码
,可以看到是使用alloc_box
创建枚举值,内部调用了swift_allocObject
,去堆
中申请空间
:
在SIL官方文档中,有介绍
alloc_box
:
从
汇编层
也可佐证
:
6.OC桥接
-
OC
枚举仅支持Int
类型,而swift
支持多种类型
。
6.1 OC
使用swift
枚举:
swift
中创建Int
类型枚举值
,使用@objc
声明
@objc
声明后,桥接文件
中,自动生成了OC
的SWIFT_ENUM
:
OC
文件中,导入swift
桥接头文件,直接调用SwiftEnum
6.2 swift
使用OC
枚举:
OC
的.h头文件
声明枚举类型:
typedef NS_ENUM(NSUInteger, OCEnum)
:自动转换成swift枚举
typedef enum
:转换成结构体
桥接文件
中,添加OC
头文件:
自动生成
的swift
文件中,可以看到转换的类型:
swift
文件中使用:
NS_ENUM
生成:可正常使用
typedef enum
生成:只能通过通过值初始化
,再使用
6.3 OC
使用swift
枚举:
- 如果
swift
中不是Int
类型,而又希望OC能用
,只能自己做个桥接
。
(例如: 原本
swift
中枚举类型
为String
,可直接通过rawValue
读取值。
为了兼容OC
,把类型改成Int
,自定义计算型属性
,禁止使用默认的rawValue
读取)
swift
中创建int类型
的枚举
,自定义string
计算属性,并禁止rawValue
的使用。
OC
中直接使用
:
- 如果还希望
OC
能访问到swift
对应的String
值:
使用
class
的类方法兼容
:
class
需要继承NSObject
,类函数
完成枚举
与String
的映射。enum
禁止rawValue
,改用string
计算属性获取
:
桥接文件
中可以看到生成了OC
的类方法
和枚举
:
OC文件
中使用:
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内容
)
有2个疑问:
-
默认属性
(字符串)是什么时候创建
的? - 如何记录
case名
和对应值
的?
默认属性
(字符串)是什么时候创建
的?
编译期
就会生成所有符号
- 所以上面
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:)
:
完整流程1.创建:
创建枚举
(格式:元组(Array<T>,Pointer)
,此例中T
为String
) ,再遍历创建
所有枚举
项。2.查询:
通过_findStringSwitchCase
获取入参值
的index
,使用switch
通过index
(int类型)匹配到case
,匹配成功
:返回optiona
l的some值
,匹配失败
,直接返回nil
在
swift
源码中搜索findStringSwitchCase
,可以看到是通过for遍历
匹配index
:
enum枚举
较为简单
,我们了解了
与其他语言
的差异
、用法
,顺带探索大小
和源码实现
。
- 下一节,介绍swift
闭包
。