C enum
C语言枚举格式
enum 枚举名 {
枚举值 1,
枚举值 2,
……
};
比如要表示星期几的枚举:
enum week {
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
第一个枚举成员的默认值为0,依次加1。要改变枚举值直接赋值就可以了。
声明一个枚举变量如下:
enum week {//这里枚举名可以省略
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} week;
Swift 枚举
同样的一个week枚举,在swift中定义如下:
enum week {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
var w: week = week.MONDAY
swift枚举不需要,
和;
,不过需要case
。可以简写只有一个case
:
enum week {
case MONDAY, TUEDAY, WEDDAY, THUDAY, FRIDAY, SATDAY, SUNDAY
}
Swift中枚举默认也是整形,区别是根据枚举的数量编译器会处理整形的类型比如:Int8
、Int16
、Int32
、Int64
。
遍历
枚举可以像集合那样遍历,需要遵循CaseIterable
协议。
enum week {
case MONDAY, TUEDAY, WEDDAY, THUDAY, FRIDAY, SATDAY, SUNDAY
}
extension week : CaseIterable {}
for enumCase in week.allCases {
print(enumCase)
}
MONDAY
TUEDAY
WEDDAY
THUDAY
FRIDAY
SATDAY
SUNDAY
rawValue(原始值)
如果想用Sring表达Enum,在Swift中可以如下:
enum week: String { //这里必须注明类型
case MONDAY = "MONDAY"
case TUEDAY = "TUEDAY"
case WEDDAY = "WEDDAY"
case THUDAY //不赋值 “THUDAY”
case FRIDAY
case SATDAY
case SUNDAY
}
在这里=
左边的值我们叫做rawValue
。想要访问rawValue
必须设置类型(Int
也需要)。
那么这里Swift是怎么处理rawValue
的呢?
var w = week.MONDAY.rawValue
查看sil
enum week : String {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
}
可以看到String
有一个别名RawValue
。有一个可选的初始化方法,有一个计算属性rawValue
。所以本质上我们访问rawValue
是在访问计算属性。
变量w
的生成过程如下:
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
alloc_global @main.w : Swift.String // id: %2
//全局变量w
%3 = global_addr @main.w : Swift.String : $*String // user: %8
%4 = metatype $@thin week.Type
//拿到MONDAY枚举
%5 = enum $week, #week.MONDAY!enumelt // user: %7
// function_ref week.rawValue.getter
//获取枚举计算属性rawValue的get方法
%6 = function_ref @main.week.rawValue.getter : Swift.String : $@convention(method) (week) -> @owned String // user: %7
//枚举值传给rawValue的get方法,返回一个String
%7 = apply %6(%5) : $@convention(method) (week) -> @owned String // user: %8
//把String给比变量w
store %7 to %3 : $*String // id: %8
%9 = integer_literal $Builtin.Int32, 0 // user: %10
%10 = struct $Int32 (%9 : $Builtin.Int32) // user: %11
return %10 : $Int32 // id: %11
} // end sil function 'main'
main.week.rawValue.getter
方法比较长,截取一部分如下:
// week.rawValue.getter
sil hidden @main.week.rawValue.getter : Swift.String : $@convention(method) (week) -> @owned String {
// %0 "self" // users: %2, %1
//接收一个枚举值参数week
bb0(%0 : $week):
//默认有个self=枚举值
debug_value %0 : $week, let, name "self", argno 1 // id: %1
//匹配枚举跳转对应分支
switch_enum %0 : $week, case #week.MONDAY!enumelt: bb1, case #week.TUEDAY!enumelt: bb2, case #week.WEDDAY!enumelt: bb3, case #week.THUDAY!enumelt: bb4, case #week.FRIDAY!enumelt: bb5, case #week.SATDAY!enumelt: bb6, case #week.SUNDAY!enumelt: bb7 // id: %2
bb1: // Preds: bb0
//构建字符串MONDAY
%3 = string_literal utf8 "MONDAY" // user: %8
%4 = integer_literal $Builtin.Word, 6 // user: %8
%5 = integer_literal $Builtin.Int1, -1 // user: %8
%6 = metatype $@thin String.Type // user: %8
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%7 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
%8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
//跳转bb8
br bb8(%8 : $String)
// %52 // user: %53
bb8(%52 : $String): // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
//返回字符串
return %52 : $String // id: %53
} // end sil function 'main.week.rawValue.getter : Swift.String'
所以在这个过程中访问rawValue
的getter
方法,根据枚举值找到对应分支构建字符串然后返回。
那么MONDAY
存在哪里呢?
可以看到是连续的内存空间来存储字符串。在程序运行的时候会通过地址+偏移量取找到这个字符串。
case和rawValue
print(week.MONDAY)
print(week.MONDAY.rawValue)
MONDAY
MONDAY
case
和rawValue
输出一样,那么这两个相同么?
显然不是一个东西,不是同一个类型。这里肯定也不能互相赋值,并且也不能对
rawValue
赋值(只有getter
方法)。
init?
上面分析的sil里面有一个init
方法
print(week.init(rawValue:"MONDAY")!)//MONDAY
print(week.init(rawValue:"123"))//nil
MONDAY
nil
可以打个符号断点具体看一下,
//走不到断点
print(week.MONDAY.rawValue)//MONDAY
//可以断点
print(week(rawValue:"MONDAY")!)//MONDAY
print(week.init(rawValue:"123"))//nil
发现访问rawValue
不走init方法,通过rawValue
初始化才走init
方法。
分析下SIL
文件
// week.init(rawValue:)
sil hidden @main.week.init(rawValue: Swift.String) -> main.week? : $@convention(method) (@owned String, @thin week.Type) -> Optional {
// %0 "rawValue" // users: %164, %158, %79, %3
// %1 "$metatype"
bb0(%0 : $String, %1 : $@thin week.Type):
%2 = alloc_stack $week, var, name "self" // users: %162, %154, %143, %132, %121, %110, %99, %88, %165, %159
debug_value %0 : $String, let, name "rawValue", argno 1 // id: %3
%4 = integer_literal $Builtin.Word, 7 // user: %6
// function_ref _allocateUninitializedArray(_:)
//调用方法创建一个数组
%5 = function_ref @Swift._allocateUninitializedArray(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
//返回一个元组
%6 = apply %5(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
//返回值是一个元组类型(Array,RawPointer当前指针)
%7 = tuple_extract %6 : $(Array, Builtin.RawPointer), 0 // users: %80, %79
%8 = tuple_extract %6 : $(Array, Builtin.RawPointer), 1 // user: %9
//访问数组首地址
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*StaticString // users: %17, %69, %59, %49, %39, %29, %19
//构建第一个字符串,存储在 array index 0
%10 = string_literal utf8 "MONDAY" // user: %12
%11 = integer_literal $Builtin.Word, 6 // user: %16
%12 = builtin "ptrtoint_Word"(%10 : $Builtin.RawPointer) : $Builtin.Word // user: %16
br bb1 // id: %13
bb1: // Preds: bb0
%14 = integer_literal $Builtin.Int8, 2 // user: %16
br bb2 // id: %15
bb2: // Preds: bb1
//构建StaticString
%16 = struct $StaticString (%12 : $Builtin.Word, %11 : $Builtin.Word, %14 : $Builtin.Int8) // user: %17
store %16 to %9 : $*StaticString // id: %17
// 1
%18 = integer_literal $Builtin.Word, 1 // user: %19
//%9数组首地址,19%存储当前数组index 1的地址
%19 = index_addr %9 : $*StaticString, %18 : $Builtin.Word // user: %27
//构建第二个字符串,存储在array index 1的位置
%20 = string_literal utf8 "TUEDAY" // user: %22
%21 = integer_literal $Builtin.Word, 6 // user: %26
%22 = builtin "ptrtoint_Word"(%20 : $Builtin.RawPointer) : $Builtin.Word // user: %26
br bb3 // id: %23
bb14: // Preds: bb13
%76 = struct $StaticString (%72 : $Builtin.Word, %71 : $Builtin.Word, %74 : $Builtin.Int8) // user: %77
store %76 to %69 : $*StaticString // id: %77
// function_ref _findStringSwitchCase(cases:string:)
//拿到函数
%78 = function_ref @Swift._findStringSwitchCase(cases: [Swift.StaticString], string: Swift.String) -> Swift.Int : $@convention(thin) (@guaranteed Array, @guaranteed String) -> Int // user: %79
//拿到数组中元素的index
%79 = apply %78(%7, %0) : $@convention(thin) (@guaranteed Array, @guaranteed String) -> Int // users: %149, %138, %127, %116, %105, %94, %83, %147, %136, %125, %114, %103, %92, %81
release_value %7 : $Array // id: %80
debug_value %79 : $Int, let, name "$match" // id: %81
//0
%82 = integer_literal $Builtin.Int64, 0 // user: %84
//拿到结构体的index
%83 = struct_extract %79 : $Int, #Int._value // user: %84
//比较两个index
%84 = builtin "cmp_eq_Int64"(%82 : $Builtin.Int64, %83 : $Builtin.Int64) : $Builtin.Int1 // user: %85
//cond_br 成功跳转bb15,否则跳转bb16
cond_br %84, bb15, bb16 // id: %85
bb15: // Preds: bb14
%86 = metatype $@thin week.Type
//返回枚举值
%87 = enum $week, #week.MONDAY!enumelt // user: %89
%88 = begin_access [modify] [static] %2 : $*week // users: %89, %90
store %87 to %88 : $*week // id: %89
end_access %88 : $*week // id: %90
br bb29 // id: %91
bb16: // Preds: bb14
debug_value %79 : $Int, let, name "$match" // id: %92
%93 = integer_literal $Builtin.Int64, 1 // user: %95
%94 = struct_extract %79 : $Int, #Int._value // user: %95
//把1和index比较,一直循环比较
%95 = builtin "cmp_eq_Int64"(%93 : $Builtin.Int64, %94 : $Builtin.Int64) : $Builtin.Int1 // user: %96
cond_br %95, bb17, bb18 // id: %96
bb28: // Preds: bb26
release_value %0 : $String // id: %158
dealloc_stack %2 : $*week // id: %159
//没有匹配到返回none,也就是nil
%160 = enum $Optional, #Optional.none!enumelt // user: %161
br bb30(%160 : $Optional) // id: %161
bb29: // Preds: bb27 bb25 bb23 bb21 bb19 bb17 bb15
%162 = load %2 : $*week // user: %163
//匹配到了
%163 = enum $Optional, #Optional.some!enumelt, %162 : $week // user: %166
release_value %0 : $String // id: %164
dealloc_stack %2 : $*week // id: %165
br bb30(%163 : $Optional) // id: %166
// %167 // user: %168
bb30(%167 : $Optional): // Preds: bb29 bb28
//返回可选值
return %167 : $Optional // id: %168
} // end sil function 'main.week.init(rawValue: Swift.String) -> main.week?'
首先构造数组,从macho文件里面逐一读取cstring存放到数组中。
这里可以看到有一个_findStringSwitchCase
方法,在swift
源码中这个方法:
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
return -1
}
接收一个数组和目标字符串。返回index
。如果找不到返回-1
。所以枚举在底层就相当于Int
。查找枚举也就是查找index。
- 创建一个Array,把所有的macho中的字符串取出来依次存储到数组中。
- 通过init参数中的值去数组中找index。然后依次与0、1、2比较。
- 通过比较index找到相等则返回对应枚举,否则返回
none
。是一个可选值。
Associated Value (关联值)
从上面看到原始值只能对应一个值。如果想用枚举表达更复杂的信息时rawValue
就不够用了。这个时候就可以使用Associated Value
比如:
enum Shape{
case circle(radious: Double) //这里标签可以省略,不推荐
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
- 添加关联值后就没有原始值了。(因为已经允许使用一组值了,通过SIL查看也没有对应的rawValue和init方法了)
- 标签可以省略
enum模式匹配
对于一般的枚举我们直接switch case匹配就可以了如:
enum week: String {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
var w: week?
switch w {
case .MONDAY: print("MONDAY")
case .SUNDAY: print("SUNDAY")
default: print("default")
}
- 不匹配所有
case
需要default,不然会报错。 - 对于多个
case
和oc
不同的是要用,
隔开匹配。
原理是把w
存储在全局变量中,匹配对应的case
做代码跳转。
对于关联值想要匹配
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
switch circle {
case let .circle(radious) : print("\(radious)")//这里相当于把常量10给了radious。
case let .rectangle(width, height): print("\(width) \(height)")
}
- 进行了
value - binding
,如果case
匹配上把值给了常量/变量。
也可以把修饰符放在变量前。
switch rectangle {
case .circle(var radious):
radious += 1
print("\(radious)")
case .rectangle(let width, var height):
height += 10
print("\(width) \(height)")
}
为了方便查看SIL
代码,简化下代码
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var circle = Shape.circle(radious: 10)
var temp: Double
var w: Int
var h: Int
switch circle {
case .circle(let radious):
temp = radious
case .rectangle(let width, var height):
w = width
h = height
}
对应sil
如下:
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
alloc_global @main.circle : main.Shape // id: %2
%3 = global_addr @main.circle : main.Shape : $*Shape // users: %9, %16
%4 = metatype $@thin Shape.Type
%5 = float_literal $Builtin.FPIEEE64, 0x4024000000000000 // 10 // user: %6
%6 = struct $Double (%5 : $Builtin.FPIEEE64) // user: %7
%7 = tuple $(radious: Double) (%6) // user: %8
//创建枚举值,枚举值关联值是元组(%6),也就是10
%8 = enum $Shape, #Shape.circle!enumelt, %7 : $(radious: Double) // user: %9
//枚举值连同关联值给到%3
store %8 to %3 : $*Shape // id: %9
alloc_global @main.temp : Swift.Double // id: %10
%11 = global_addr @main.temp : Swift.Double : $*Double // user: %23
alloc_global @main.w : Swift.Int // id: %12
%13 = global_addr @main.w : Swift.Int : $*Int // user: %33
alloc_global @main.h : Swift.Int // id: %14
%15 = global_addr @main.h : Swift.Int : $*Int // user: %36
%16 = begin_access [read] [dynamic] %3 : $*Shape // users: %17, %18
//读取%3
%17 = load %16 : $*Shape // user: %19
end_access %16 : $*Shape // id: %18
//跳转bb1
switch_enum %17 : $Shape, case #Shape.circle!enumelt: bb1, case #Shape.rectangle!enumelt: bb2 // id: %19
// %20 // user: %21
bb1(%20 : $(radious: Double)): // Preds: bb0
//取出关联值
%21 = tuple_extract %20 : $(radious: Double), 0 // users: %24, %22
//关联值给到radious
debug_value %21 : $Double, let, name "radious" // id: %22
%23 = begin_access [modify] [dynamic] %11 : $*Double // users: %24, %25
store %21 to %23 : $*Double // id: %24
end_access %23 : $*Double // id: %25
br bb3 // id: %26
这也就解释了为什么switch中能访问到关联值。
1.构建一个元组
2.根据当前枚举值匹配case
3.取出元组值,声明常量使用这个值初始化
只匹配一个case的情况,除了写default的情况还可以通过if case
处理
if case let Shape.circle(radious) = circle {
print("\(radious)")
}
这里SIL
代码和switch
差不多,原理相同。
多个case匹配
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
case square(width: Int, height: Int)
}
var rectangle = Shape.rectangle(width: 10, height: 10)
var square = Shape.square(width: 10, height: 20)
var temp: Int
switch rectangle {
case let .rectangle(10, x), let .square(10, x):
temp = x
default:
print("not found case")
}
对应的sil
(省略了main中的switch_enum相关逻辑,和上面的一样):
// %28 // users: %30, %29
bb1(%28 : $(width: Int, height: Int)): // Preds: bb0
//取出关联值 width
%29 = tuple_extract %28 : $(width: Int, height: Int), 0 // users: %31, %33
//取出关联值 height
%30 = tuple_extract %28 : $(width: Int, height: Int), 1 // users: %36, %37
debug_value %29 : $Int, let, name "$match" // id: %31
//常量 10
%32 = integer_literal $Builtin.Int64, 10 // user: %34
//从结构体取 width
%33 = struct_extract %29 : $Int, #Int._value // user: %34
//比较 10 和 width
%34 = builtin "cmp_eq_Int64"(%32 : $Builtin.Int64, %33 : $Builtin.Int64) : $Builtin.Int1 // user: %35
//相等走bb2,否则走bb3
cond_br %34, bb2, bb3 // id: %35
bb2: // Preds: bb1
//将 height 给到 x
debug_value %30 : $Int, let, name "x" // id: %36
//bb6是将x给到temp
br bb6(%30 : $Int) // id: %37
取出case元组值,比较第一个元素,赋值第二个元素。
如果匹配的第一个元素是通配符呢?
switch rectangle {
case let .rectangle(_, x), let .square(_, x):
temp = x
default:
print("not found case")
}
可以看到直接取了height
赋值给x
了,这里就不需要检查第一个元素了。
// %28 // user: %29
bb1(%28 : $(width: Int, height: Int)): // Preds: bb0
%29 = tuple_extract %28 : $(width: Int, height: Int), 1 // users: %30, %31
debug_value %29 : $Int, let, name "x" // id: %30
br bb3(%29 : $Int) // id: %31
对于一个case多个匹配的情况,常量/变量要匹配,如下:
对于.rectangle
有两个参数,.square
有3个参数。
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
case square(width: Int, height: Int, length: Int)
}
var rectangle = Shape.rectangle(width: 10, height: 10)
var square = Shape.square(width: 10, height: 20, length: 30)
var temp: Int
switch square {
case let .rectangle(20, x), let .square(10, x, 30):
temp = x
print("\(temp)")
default:
print("not found case")
}
在上面.square
的第三个参数必须是一个确定的值,当然也可以第二个参数是确定值,第三个参数是x
switch square {
case let .rectangle(20, x), let .square(10, 10, x):
temp = x
print("\(temp)")
default:
print("not found case")
}
也就是说case里面的常量/变量每个case必须都有。
嵌套
如果一个复杂枚举是由单个枚举构成的,那么我们可以用嵌套来解决。
enum CombineDirect {
enum BaseDirect {
case up
case down
case left
case right
}
case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}
let combine = CombineDirect.leftUp(combineElement1: .left, combineElement2: .up)
当然在结构体中也可以嵌套enum:
struct Skill {
enum KeyType {
case up
case down
case left
case right
}
let key: KeyType
func launchSkill() {
switch key {
case .left,.right:
print("left, right")
case .down,.up:
print("up, down")
}
}
}
实际上枚举只是在不同作用于内,结构上没有影响
enum属性
enum
中能包含计算属性(本身是方法)、类型属性(本身是全局变量)。不能包含存储属性。(结构体能包含存储属性是因为结构体大小就是存储属性大小)
enum Shape{
case circle(radious: Double)
case rectangle(width: Int, height: Int)
static var height = 20
// var width: Double
var width: Int {
get {
return 10
}
}
}
计算属性要通过case
调用,类型属性通过枚举调用:
print(Shape.rectangle(width: 10, height: 10).width)
print(Shape.height)
enum方法
如果想知道星期枚举的下一天:
enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
func nextDay() -> WEEK {
if self == .SUNDAY {
return WEEK(rawValue: 0)!
} else {
return WEEK(rawValue: self.rawValue + 1)!
}
}
}
print(WEEK.SUNDAY.nextDay())
这样做的好处是我们不需要在外面再专门定义一个方法了,直接在枚举中就可以定义方法处理了。
- 可以定义实例方法和类型方法(static)
enum大小
rawValue枚举大小
enum Hotpot {
case cat
}
var hotpot = Hotpot.cat
print(MemoryLayout.size(ofValue: hotpot))
print(MemoryLayout.size)
print(MemoryLayout.stride)//步长为1
0
0
1
可以看到size
为0
,stride
为1
。那么增加一个case
看下:
enum Hotpot {
case cat
case dog
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
1
1
size
和stride
都为1
,那也就意味着enum
在内存存储过程当中就是以1
个字节长度存储的。
声明一个变量打个断点看一下:
var hotpot = Hotpot.cat
这里把
0x0
给到了[rip + 0x49c8]
,那么修改一下:
var hotpot = Hotpot.dog
变成了把
0x1
给到[rip + 0x49c8]
。
这里对于默认的
enum
,case
是UInt8
也就是1
字节。最大255
,超过会自动变成UInt16
。
验证下:
enum Hotpot {
case cat1
……
case cat257
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
2
2
断点验证:
var hotpot = Hotpot.cat257
0x100
也就是256
。从0
开始对应Hotpot.cat257
。
读取一下内存地址:
var hotpot = Hotpot.cat1
var hotpot1 = Hotpot.cat2
var hotpot2 = Hotpot.cat3
var hotpot3 = Hotpot.cat4
枚举数量小于256
:
(lldb) po withUnsafePointer(to: &hotpot){print($0)}
0x000000010000805c
0 elements
(lldb) memory read 0x000000010000805c
0x10000805c: 00 01 02 03 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x10000806c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
枚举数量大于256
:
(lldb) po withUnsafePointer(to: &hotpot){print($0)}
0x000000010000c05c
0 elements
(lldb) memory read 0x000000010000c05c
0x10000c05c: 00 00 01 00 02 00 03 00 00 00 00 00 00 00 00 00 ................
0x10000c06c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
那么在内存中通过分配对应字节大小(这里分别是1和2),0x1、0x2……
都存储在内存中。
- 如果枚举有原始值,大小取决于case的多少。
- 枚举类型其实标识的是rawValue的值,不是case的值。
- 对于只有一个case的情况是0,因为这个enum没有意义,多于一个则有意义。
关联值枚举大小
只有一个case的情况:
enum Shape {
case circle(radious: Double)
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
8
8
多个case的情况:
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
17
24
这里size
以rectangle
的大小为准8 (width)+ 8(height) + 1(case)
的大小为17
。
内存验证下:
var rectangle = Shape.rectangle(width: 10, height: 20)
把rectangle
修改下:
enum Shape {
case circle(radious: Double)
case rectangle(width: Int)
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
var rectangle = Shape.rectangle(width: 10)
size
为9
,补齐8 stride
变为16
。
- 有关联值的枚举大小取决于最大的那个case+1(这里1为case本身,如果只有一个case则不加1)。
嵌套枚举大小
enum CombineDirect {
enum BaseDirect {
case up //00
case down //01
case left //02
case right //03
}
//0
case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
//4
case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
//8
case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
//12
case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}
var combine = CombineDirect.leftDown(combineElement1: .left, combineElement2: .down)
print(MemoryLayout.size)
print(MemoryLayout.stride)
这里
02
和BaseDirect相关,例子中是.left
,81
中的1
(因为BaseDirect
占一字节)和第二个枚举值相关.down 01
,系统进行了优化。8
代表case leftDown
。
这里并不是递增,如果
case
很多的话会从0、1、2、3、4开始,编译器做了优化
- 本质上取决于
BaseDirect
枚举的大小。
结构体中枚举大小
struct Skill {
enum KeyType {
case up
case down
case left
case right
}
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
0
1
- 结构体中枚举大小与只有一个case的情况相同(有关联值的枚举也一样)
这里相当于结构体中没有定义枚举,相当于没有。其实也就是结构体的大小。
内存对齐&字节对齐
内存对齐
8字节对齐
字节对齐
倍数对齐
struct Hotpot {
var age: Int //8
var height: UInt8 //1
var width: UInt16 //2
}
12
16
按理说应该是11
啊,调整下顺序:
struct Hotpot {
var age: Int //8
var width: UInt16 //2
var height: UInt8 //1
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
11
16
这里是因为编译器做了优化,这里UInt8
编译器做了优化,为了偶地址。
上面说的结构体,枚举也一样:
enum Hotpot {
//1
case cat
// 8 + 2 + 1 + 1(case)
case cat1(Int,UInt16,UInt8)
//8 + 2(偶地址) + 2 + 1(case)
case cat2(Int,UInt8,UInt16)
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
13
16
- 计算
size
的时候要考虑进去偶地址 - 计算
size
的时候要考虑进去字节对齐
枚举大小案例
enum Shape{
case circle(radious: UInt8)
//2 + 8 + 2 + 1 = 13
case rectangle(width: UInt8, height: Int, length: UInt16)
}
print(MemoryLayout.size)
print(MemoryLayout.stride)
18
24
按照前面所说应该是2 + 8 + 2 + 1 = 13
,这里由于需要8
字节对齐,所以应该是8 + 8 + 2 + 1 = 19
为什么是18
呢?
编译器在这里做了优化
var shape = Shape.rectangle(width: 1, height: 2, length: 3)
memory read这里是小端,验证了
width
占了8字节,case1
字节放在了0x80
,这个是枚举值的标志位。相当于对case做了优化。
rectangle
换个顺序看下:
case rectangle(width: UInt8,length: UInt16, height: Int)
应该是(2 + 2)(这里补齐到8) + 8 + 1(优化到前面) = 16
16
16
可以看到
width
、height
和case
存在了前8字节上。01
存储的就是width
偶地址补位为0001
,0002
存储的就是length
补3字节000000 0002
,剩余1字节存放case``ox80
。
枚举大小总结
-
rawValue
的枚举大小默认是1
字节(UInt8
),case超过256
个会UInt8->UInt16->UInt32……
。 - 关联值枚举大小与最大
case
关联值相关,需要加1
(case
)和考虑偶地址
以及字节对齐。
indirect
如果enum
是一个复杂的数据结构,可以通过indirect
让enum
简洁,比如要用结构体写一个链表:
enum List {
case end
case node(Element,next:List)
}
这样写直接报错:
系统要求增加
indirect
关键字。
由于
enum
是值类型,大小在编译期就确定了,在上面的例子中List
大小并不能确定(由于case中有List)。
官方原文:
You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.
enum BinaryTree {
case empty
indirect case node(left: BinaryTree,value:T, right:BinaryTree)
}
var node = BinaryTree.node(left: BinaryTree.empty, value: 10, right: BinaryTree.node(left: BinaryTree.empty, value: 20, right: BinaryTree.empty))
print(MemoryLayout.size(ofValue: node))
print(MemoryLayout.stride(ofValue: node))
indirect
也可以放在枚举之前:
indirect enum BinaryTree {
case empty
case node(left: BinaryTree,value:T, right:BinaryTree)
}
8
8
可以看到大小都是8
,读取下内存:
看内存结构像是
HeapObject
,分析下SIL
:
alloc_box
sil-instruction ::= 'alloc_box' sil-type (',' debug-var-attr)* %1 = alloc_box $T // %1 has type $@box T
Allocates a reference-counted @box on the heap large enough to hold a value of type T, along with a retain count and any other metadata required by the runtime. The result of the instruction is the reference-counted @box reference that owns the box. The project_box instruction is used to retrieve the address of the value inside the box.
The box will be initialized with a retain count of 1; the storage will be uninitialized. The box owns the contained value, and releasing it to a retain count of zero destroys the contained value as if by destroy_addr. Releasing a box is undefined behavior if the box's value is uninitialized. To deallocate a box whose value has not been initialized, dealloc_box should be used.
大概意思是在堆空间上分配了一块内存区域,内存区域用来存放value
,在例子中也就是Int
值。在取值的时候取box
,box
是把value
包裹了一层,也就是取的是地址。
alloc_box
本质是swift_allocObject
,所以indirect
就是通知编译器当前的枚举是递归的,大小自然无法确定,所以分配堆空间存放。
两种方式的比较:
indirect enum BinaryTree {
case empty
case node(left: BinaryTree,value:T, right:BinaryTree)
}
enum BinaryTree1 {
case empty
indirect case node(left: BinaryTree1,value:T, right:BinaryTree1)
}
var node = BinaryTree.node(left: BinaryTree.empty, value: 10, right: BinaryTree.empty)
var node1 = BinaryTree.empty
var node2 = BinaryTree1.node(left: BinaryTree1.empty, value: 10, right: BinaryTree1.empty)
var node3 = BinaryTree1.empty
node
node1
node2
node3
indrect
关键字的本质把对应case的值放在了堆空间上。- 通知编译器当前的枚举是递归的,分配堆空间存放。
- 放在
case
前这个case
是引用类型,在enum
前整个枚举类型都用引用类型存储。
Swift与OC枚举混编
-
Swift
枚举非常强大,可以添加方法、extension、计算属性、类型属性。 -
OC
枚举仅仅是一个整形值。
Swift枚举暴露给OC
- @objc标记
- 枚举应该是Int类型
@objc enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
暴露给OC的是:
typedef SWIFT_ENUM(NSInteger, WEEK, closed) {
WEEKMONDAY = 0,
WEEKTUEDAY = 1,
WEEKWEDDAY = 2,
WEEKTHUDAY = 3,
WEEKFRIDAY = 4,
WEEKSATDAY = 5,
WEEKSUNDAY = 6,
};
oc中调用
#import "SwiftEnum-Swift.h"
WEEK week = WEEKMONDAY;
那么对于非Int类型的枚举呢?
1.通过swift中包装一层,也就是Int枚举,只不过Swift中封装个方法调用。对OC来说转换的只是case,没有方法。
@objc public enum WEEK: Int {
case MONDAY
case TUEDAY
case WEDDAY
func weekName() -> String {
switch self {
case .MONDAY: return "MONDAY"
case .TUEDAY: return "TUEDAY"
case .WEDDAY: return "WEDDAY"
}
}
}
swift中调用
var week = WEEK.MONDAY.weekName()
oc中调用
WEEK week = WEEKMONDAY;
2.通过class封装,这里相当于把class共享给OC了,自然能访问到enum。这个时候通过class getName
方法 OC
也能读取rawValue
class WeekName: NSObject {
@objc enum WEEK : Int {
case MONDAY
case TUEDAY
case WEDDAY
var weekName: String {
return WeekName.getName(self)
}
}
@objc class func getName(_ fieldName:WEEK) -> String {
switch fieldName {
case .MONDAY: return "MONDAY"
case .TUEDAY: return "TUEDAY"
case .WEDDAY: return "WEDDAY"
}
}
}
转换如下:
enum WEEK : NSInteger;
SWIFT_CLASS("_TtC9SwiftEnum8WeekName")
@interface WeekName : NSObject
+ (NSString * _Nonnull)getName:(enum WEEK)fieldName SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
typedef SWIFT_ENUM(NSInteger, WEEK, closed) {
WEEKMONDAY = 0,
WEEKTUEDAY = 1,
WEEKWEDDAY = 2,
};
swift中调用
var week = WeekName.WEEK.MONDAY. weekName
oc中调用
WEEK week = WEEKMONDAY;
NSString *str = [WeekName getName:week];
在这里如果不想swift中使用Int枚举,可以重写rawValue
,这里需要注意不能private
,可以直接assert
让debug
下crash
Property 'rawValue' must be declared public because it matches a requirement in public protocol 'RawRepresentable'
public var rawValue: Int {
assert(false, "This is a string enum,please use weekName()")
return 0
}
swift调用OC枚举
NS_ENUM枚举
在oc中使用NS_ENUM
定义的枚举都会自动转化为swift
中的枚举
typedef NS_ENUM(NSUInteger, OCEnum) {
value1,
value2
};
自动转换代码:
public enum OCEnum : UInt {
case value1 = 0
case value2 = 1
}
在swift中调用需要在bridging中导入头文件:
//SwiftEnum-Bridging-Header.h中
#import "OCTest.h"
//调用:
var ocEnum = OCEnum.value1
非NS_ENUM枚举
不是NS_ENUM
宏定义的枚举,或者用NS_OPTIONS定义的枚举:
typedef enum {
num1,
num2
} OCNum;
可以看到转换成了结构体:
public struct OCNum : Equatable, RawRepresentable {
public init(_ rawValue: UInt32)
public init(rawValue: UInt32)
public var rawValue: UInt32
}
open class OCTest : NSObject {
}
swift调用:
var ocNumEnum = OCNum.init(rawValue: 1)
print(ocNumEnum)
输出
OCNum(rawValue: 1)
c的enum
typedef NS_ENUM(NSInteger, CEnum) {
CEnumInValid = 0,
CEnumA = 1,
CEnumB = 2,
CEnumC = 3,
};
转换后代码
public enum CEnum : Int {
case inValid = 0
case A = 1
case B = 2
case C = 3
}
调用
var cEnum = CEnum.A
cEnum = CEnum.init(rawValue: 10)!
print(cEnum.rawValue)
10
对比swift
的enum
var swiftEnum = WEEK.init(rawValue: 10)
print(swiftEnum?.rawValue)
nil
对比可知,在swift
中调用oc/c
的枚举,对于不存在的case
会返回rawValue
,对于纯swift
枚举返回nil
。