C语⾔枚举
先来回顾⼀下
C
语⾔的枚举写法:
enum 枚举名 {
枚举值1,
枚举值2,
......
};
⽐如表示⼀周 7天,⽤
C
语⾔的枚举写法应该是这样的:
enum week {
MON, TUE, WED, THU, FRI, SAT, SUN
};
第⼀个枚举成员默认值为
0
,后⾯枚举值依次类推。如果更改只需这样操作:
enum week {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
定义⼀个枚举变量
week
:
enum Week {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
} week;
同样是定义⼀个枚举变量
week
,省略枚举名称:
enum {
MON = 1, TUE, WED, THU, FRI, SAT, SUN
} week;
Swift中的枚举
枚举基础
⽐如表示⼀周 7天,⽤
Swift
的枚举写法应该是这样的:
enum Week {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
上述代码也可以直接⼀个
case
,然后⽤逗号隔开:
enum Week {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
Swift
中可以创建String
类型枚举。在Swift
中=
左边的值叫枚举值,右边的叫rawValue
原始值,case 枚举值 = rawValue原始值
enum Week: String {
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
如果不想写后⾯的字符串,这时可以使⽤隐⼠
rawValue
分配。未指定类型的枚举,使用rawValue
属性,编译报错
Int
类型枚举,枚举值FRI
分配rawValue
为10
,MON
依然是从0
开始,后⾯枚举值依次类推。在FRI
之后的枚举值从11
开始,依次类推
enum Week: Int {
case MON, TUE, WED, THU, FRI = 10, SAT, SUN
}
print("MON:\(Week.MON.rawValue),SAT:\(Week.SAT.rawValue)")
//输出以下内容:
//MON:0,SAT:11
String
类型枚举,枚举值FRI
分配rawValue
打印Hello
,其他未分配rawValue
打印自身枚举值
enum Week: String {
case MON, TUE, WED, THU, FRI = "Hello", SAT, SUN
}
print("MON:\(Week.MON.rawValue),FRI:\(Week.FRI.rawValue),SUN:\(Week.SUN.rawValue)")
//输出以下内容:
//MON:MON,FRI:Hello,SUN:SUN
通过SIL代码,分析
String
类型枚举,是如何打印rawValue
的
enum Week: String {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
let w: Week = .MON
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
enum Week : String {
case MON, TUE, WED, THU, FRI, SAT, SUN
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
}
SIL代码的枚举声明除了
case
还多了一些东西:
- 首先通过
typealias
取别名,在枚举Week
里把String
取名为RawValue
- 生成可选的初始化方法
init?(rawValue: String)
,也就是说初始化可以返回nil
- 生成
rawValue
计算属性,所以在代码中访问rawValue
属性,本质就是访问它的get
方法
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
alloc_global @main.w : Swift.String // id: %2
%3 = global_addr @main.w : Swift.String : $*String // user: %8
%4 = metatype $@thin Week.Type
%5 = enum $Week, #Week.MON!enumelt // user: %7
// function_ref Week.rawValue.getter
%6 = function_ref @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String // user: %7
%7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
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
方法:
%5
接收枚举值%6
获取Week.rawValue.getter
方法地址- 通过
apply
调用getter
方法%6
,传入参数枚举值%5
,将返回值赋值给%7
- 将返回结果
%7
存储到%3
// Week.rawValue.getter
sil hidden @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $Week):
debug_value %0 : $Week, let, name "self", argno 1 // id: %1
switch_enum %0 : $Week, case #Week.MON!enumelt: bb1, case #Week.TUE!enumelt: bb2, case #Week.WED!enumelt: bb3, case #Week.THU!enumelt: bb4, case #Week.FRI!enumelt: bb5, case #Week.SAT!enumelt: bb6, case #Week.SUN!enumelt: bb7 // id: %2
bb1: // Preds: bb0
%3 = string_literal utf8 "MON" // user: %8
%4 = integer_literal $Builtin.Word, 3 // 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
br bb8(%8 : $String) // id: %9
bb2: // Preds: bb0
%10 = string_literal utf8 "TUE" // user: %15
%11 = integer_literal $Builtin.Word, 3 // user: %15
%12 = integer_literal $Builtin.Int1, -1 // user: %15
%13 = metatype $@thin String.Type // user: %15
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%14 = 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: %15
%15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
br bb8(%15 : $String) // id: %16
bb3: // Preds: bb0
%17 = string_literal utf8 "WED" // user: %22
%18 = integer_literal $Builtin.Word, 3 // user: %22
%19 = integer_literal $Builtin.Int1, -1 // user: %22
%20 = metatype $@thin String.Type // user: %22
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%21 = 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: %22
%22 = apply %21(%17, %18, %19, %20) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %23
br bb8(%22 : $String) // id: %23
bb4: // Preds: bb0
%24 = string_literal utf8 "THU" // user: %29
%25 = integer_literal $Builtin.Word, 3 // user: %29
%26 = integer_literal $Builtin.Int1, -1 // user: %29
%27 = metatype $@thin String.Type // user: %29
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%28 = 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: %29
%29 = apply %28(%24, %25, %26, %27) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %30
br bb8(%29 : $String) // id: %30
bb5: // Preds: bb0
%31 = string_literal utf8 "FRI" // user: %36
%32 = integer_literal $Builtin.Word, 3 // user: %36
%33 = integer_literal $Builtin.Int1, -1 // user: %36
%34 = metatype $@thin String.Type // user: %36
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%35 = 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: %36
%36 = apply %35(%31, %32, %33, %34) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %37
br bb8(%36 : $String) // id: %37
bb6: // Preds: bb0
%38 = string_literal utf8 "SAT" // user: %43
%39 = integer_literal $Builtin.Word, 3 // user: %43
%40 = integer_literal $Builtin.Int1, -1 // user: %43
%41 = metatype $@thin String.Type // user: %43
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%42 = 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: %43
%43 = apply %42(%38, %39, %40, %41) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %44
br bb8(%43 : $String) // id: %44
bb7: // Preds: bb0
%45 = string_literal utf8 "SUN" // user: %50
%46 = integer_literal $Builtin.Word, 3 // user: %50
%47 = integer_literal $Builtin.Int1, -1 // user: %50
%48 = metatype $@thin String.Type // user: %50
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%49 = 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: %50
%50 = apply %49(%45, %46, %47, %48) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %51
br bb8(%50 : $String) // id: %51
// %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'
Week.rawValue.getter
方法:
bb0
,接收一个枚举值,self
就是该枚举值,通过switch_enum
匹配跳转到对应bb1
至bb7
代码分支bb1
至bb7
,构建出对应枚举值的字符串,最终都调用bb8
bb8
返回对应字符串
rawValue.getter
返回的字符串在编译时期已经存储好,通过Mach-O
文件查看它们的存储位置
rawValue
的字符串存储在__TEXT.__cstring
段,而且内存地址是连续的。在上述SIL代码中构建枚举值字符串,本质就是从Mach-O
里把对应地址的字符串取出来
区分case和rawValue
enum Week: String {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
print("case枚举值:\(Week.MON)")
print("rawValue原始值:\(Week.MON.rawValue)")
//输出以下内容:
//case枚举值:MON
//rawValue原始值:MON
上述代码中,
做一个简单的测试,如果将case
枚举值Week.MON
和rawValue
原始值Week.MON.rawValue
打印出的内容完全一致,但一个是输出枚举值,一个是访问rawValue
的get
方法,它们是完全不同的东西
String
类型的Week.MON.rawValue
赋值给Week
类型week1
,再将Week
类型Week.MON
赋值给String
类型week2
,都会编译报错
init方法的调用时机
添加符号断点
Week.init
,预期断住枚举初始化方法
enum Week: String {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
let week: Week = Week.MON
print("case枚举值:\(week)")
print("rawValue原始值:\(week.rawValue)")
//输出以下内容:
//case枚举值:MON
//rawValue原始值:MON
运行上述代码,没有达到预期,没有任何断点被触发
enum Week: String {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
print(Week(rawValue: "MON"))
print(Week.init(rawValue: "TUE"))
print(Week(rawValue: "Hello"))
//输出以下内容:
//Optional(LGSwiftTest.Week.MON)
//Optional(LGSwiftTest.Week.TUE)
//nil
修改上述代码,使用
Week(rawValue:)
或Week.init(rawValue:)
成功触发断点
继续执行代码,从运行结果来看,前两个打印
Optional
可选值,第三个打印nil
,因为找不到对应Hello
的枚举值
通过SIL代码,分析枚举的初始化构造函数
enum Week: String {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
print(Week(rawValue: "Hello"))
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
来到
Week.init(rawValue:)
方法
来到
bb1
方法
从
bb1
至bb14
,将所有字符串存储到数组内,进行case
匹配
通过源码查看
_findStringSwitchCase
方法,两个入参分别是数组和需要匹配的字符串,然后遍历数组,如果匹配成功返回对应index
,如果匹配失败返回-1
bb15
匹配成功,直接跳转bb29
。bb16
匹配失败,继续后续的匹配
bb28
全部匹配失败,构建一个.none
类型的Optional
返回,就是nil
。bb29
匹配成功,构建一个.some
类型的Optional
返回,就是对应的枚举值
关联值枚举
如果想⽤枚举表达更复杂的信息,⽽不仅仅是⼀个
rawValue
这么简单,这个时候可以使⽤关联值
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
关联值枚举,没有
rawValue
属性。因为关联值枚举可以使用单个值或一组值来表示,但rawValue
只能针对单个值
将上述代码生成SIL文件:
只有swiftc -emit-sil main.swift | xcrun swift-demangle
case
和关联值,没有typealias
取别名,没有init
方法,没有rawValue
计算属性
关联值枚举可以省略关联值的标签,例如
radious
、width
、height
,但并不推荐这种书写方式,因为可读性太差
enum Shape {
case circle(Double)
case rectangle(Int, Int)
}
关联值枚举的使用
//创建
var circle = Shape.circle(radious: 10.0)
//重新分配
circle=Shape.circle(radious: 20.0)
模式匹配
使⽤
switch
匹配enum
的时候,必须列举当前所有可能的情况,否则编译报错
匹配
enum
可以列举出所有情况,也可以使用default
表示默认情况
enum Week: String {
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
var week: Week?
switch week {
case .MON:
print(Week.MON.rawValue)
default:
print("unknow day")
}
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
匹配关联值枚举
- 方式一:通过
switch
匹配所有case
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var shape = Shape.circle(radious: 10.0)
switch shape {
case let .circle(radious):
print("circle-radious:\(radious)")
case let .rectangle(width, height):
print("rectangle-width:\(width),height:\(height)")
}
//输出以下内容:
//circle-radious:10.0
case let .circle(radious)
相当于做了value-binding
,如果case
匹配上,相当于把10.0
赋值给常量radious
另一种写法:将关联值的参数使用
let
、var
修饰
switch shape {
case .circle(var radious):
print("circle-radious:\(radious)")
case .rectangle(let width, let height):
print("rectangle-width:\(width),height:\(height)")
}
通过SIL代码,查看关联值枚举的匹配模式
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var shape = Shape.circle(radious: 10.0)
var temR: Double
var w: Int
var h: Int
switch shape {
case .circle(let radious):
temR=radious
case .rectangle(let width, let height):
w=width
h=height
}
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
- 方式二:通过单个
case
进行匹配
enum Shape {
case circle(radious: Double)
case rectangle(width: Int, height: Int)
}
var shape = Shape.circle(radious: 10.0)
if case let Shape.circle(radious) = shape {
print("circle-radious:\(radious)")
}
//输出以下内容:
//circle-radious:10.0
匹配不同枚举值的相同关联值
enum Shape {
case circle(radious: Double, diameter: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, width: Double)
}
var shape = Shape.circle(radious: 10.0, diameter: 20.0)
switch shape {
case let .circle(x, 20.0), let .square(x, 20.0):
print("x:\(x)")
default:
print("default")
}
//输出以下内容:
//x:10.0
上述代码,将多个枚举值中,我们想要匹配的关联值用
x
代替。如果枚举值为circle
或square
,且第二个关联值为20.0
,即为匹配成功
通过SIL代码,查看不同枚举值的相同关联值是如何匹配的
enum Shape {
case circle(radious: Double, diameter: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, width: Double)
}
var shape = Shape.circle(radious: 10.0, diameter: 20.0)
var tmpR: Double
switch shape {
case let .circle(x, 20.0), let .square(x, 20.0):
tmpR=x
default:
print("default")
}
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
不同枚举值里,用到匹配的变量或常量
x
,必须名称相同,不能一个用x
一个用y
,否则编译报错
使用通配符
_
方式匹配
enum Shape {
case circle(radious: Double, diameter: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, width: Double)
}
var shape = Shape.circle(radious: 10.0, diameter: 30.0)
switch shape {
case let .circle(x, _), let .square(x, _):
print("x:\(x)")
default:
print("default")
}
//输出以下内容:
//x:10.0
上述代码,我们不关心第二个关联值是什么,可以使用通配符
_
代替。如果枚举值为circle
或square
,第二个关联为任意值,都能匹配成功
同样使用单个
case
进行匹配,也可以使用通配符_
var shape = Shape.circle(radious: 10.0, diameter: 20.0)
if case let Shape.circle(x, _) = shape{
print("x:\(x)")
}
//输出以下内容:
//x:10.0
枚举嵌套
enum CombineDirect{
enum BaseDirect{
case up
case down
case left
case right
}
case leftUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case leftDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
}
var combind = CombineDirect.leftDown(baseDirect1: .left, baseDirect2: .down)
上述代码,通过
BaseDirect
枚举的up
、down
、left
、right
四个case
,组合出CombineDirect
枚举中leftUp
、rightUp
、leftDown
、rightDown
四个case
。这种方式下BaseDirect
枚举相当于是私有的,外界无法直接访问
结构体中嵌套枚举
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 .up,.down:
print("up, down")
}
}
}
let kill = Skill.init(key: .up)
kill.launchSkill()
//输出以下内容:
//up, down
枚举中包含属性
enum
中能包含计算属性,类型属性,不能包含存储属性
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
var width: Double{
get{
return 10.0
}
}
static let height = 20.0
}
enum
中包含存储属性,编译报错
结构体可以包含存储属性,因为结构体的大小就是存储属性的大小。但enum
大小取决于case
的个数,只要case
个数没有超过255
,enum
的大小就是1字节
- 计算属性本质是
get
、set
方法,对于值类型来说根本不用存储方法- 类型属性是全局变量,它的存储也和
enum
没有任何关系
枚举中包含⽅法
可以在
enum
中定义实例⽅法,static
修饰的⽅法
enum Week: Int {
case MON, TUE, WED, THU, FRI, SAT, SUN
mutating func nextDay(){
if self == .SUN {
self = Week(rawValue: 0)!
}
else {
self = Week(rawValue: self.rawValue + 1)!
}
}
}
var week: Week = .SUN
week.nextDay()
print(week)
在
enum
的nextDay
方法中修改自身,需要使用mutating
关键字修饰
枚举的大小
rawValue
枚举值大小
enum NoMean{
case a
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:1
//size:0
enum
中只有一个case
,大小为0,步长为1。当只有一个case
的枚举,大小为0表示这个enum
是没有意义的
enum NoMean{
case a
case b
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:1
//size:1
enum
中有两个case
,大小为1,步长为1
enum NoMean{
case a
case b
case c
case d
case e
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:1
//size:1
enum
中存在更多case
,依然是大小为1,步长为1
将枚举值
a
、b
、c
赋值给三个常量
通过断点查看汇编代码,分析枚举值
通过断点可以看出a
、b
、c
a
、b
、c
分别是0x0
、0x1
、0x2
,对系统来说就是0
、1
、2
。所以rawValue
枚举值默认是UInt8
类型,占1字节
,最大可以存储255
。超过255
个枚举值,系统会将UInt8
升级为UInt16
、UInt32
、UInt64
通过
当前枚举的步⻓是lldb
查看内存
1字节
,也就意味着如果在内存中连续存储NoMean
,需要跨越1字节
的⻓度。1字节
也就是8 位
,最⼤可以表达的数字是255
关联值枚举的大小
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:24
//size:17
关联值枚举⼤⼩,取决于最⼤关联值⼤⼩,并加上
1字节
枚举值大小。
circle
有一个Double
类型关联值,占8字节
rectangle
有两个Double
类型关联值,占16字节
enum
的大小,就是最大关联值16字节
,再加枚举值1字节
,共占17字节
stride
由于8字节
对齐,所以自动补齐到24字节
通过
lldb
查看内存
枚举嵌套的大小
enum CombineDirect{
enum BaseDirect{
case up
case down
case left
case right
}
case leftUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case leftDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:2
//size:2
枚举嵌套和关联值枚举一样,同样取决于关联值大小
通过
lldb
查看内存
BaseDirect
中的up
、down
、left
、right
对应的枚举值分别为0
、1
、2
、3
CombineDirect
中的leftUp
、rightUp
、leftDown
、rightDown
对应的枚举值分别为0
、4
、8
、12
。这里并没有规律可寻,如果加入更多case
,也会变成0
、1
、2
、3
...向后递增,通过源码分析目前还未找到相关定义- 图中输出的
02
是left
的枚举值,81
要拆开来看,8
是leftDown
的枚举值,1
是down
的枚举值enum
大小占2字节
,因为leftDown
的枚举值和down
的枚举值存储在同一字节内,属于系统优化
结构体中嵌套枚举的大小
struct Skill{
enum KeyType{
case up
case down
case left
case right
}
let key: KeyType
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:1
//size:1
结构体中有一个
KeyType
枚举类型的成员变量key
,所以结构体大小为1,步长为1
indirect关键字
如果想要表达的
enum
是⼀个复杂的关键数据结构,可以通过indrect
关键字让当前的enum
更简洁
创建链表结构
enum
,对应当前递归枚举来说,不添加indirect
关键字,编译报错
因为
enum
是值类型,它会在编译时期确定大小。但对于接收泛型T
的enum
,编译时期无法确定enum
大小,系统无法分配空间
enum List{
case end
indirect case node(T, next: List)
}
上述代码,在
case node
前面添加indirect
关键字,可以编译通过
indirect enum List{
case end
case node(T, next: List)
}
另一种方式,可以在
enum List
前面添加indirect
关键字,同样可以编译通过
indirect enum List{
case end
case node(T, next: List)
}
print("List stride:\(MemoryLayout>.stride)")
print("List size:\(MemoryLayout>.size)")
print("List stride:\(MemoryLayout>.stride)")
print("List size:\(MemoryLayout>.size)")
上述代码,分别将
Int
和String
传入enum
,打印出来的大小都是8字节
,下面来分析一下原因
通过
lldb
查看内存
打印case end
,存储的是end
枚举值
打印
case node
,存储的是堆区地址
indirect
关键字本质就是通知编译器,当前enum
是递归枚举,无法确定大小,需要在堆区空间分配内存,并存储enum
通过SIL代码,查看
indirect
关键字,如何分配堆区内存空间
indirect enum List{
case end
case node(T, next: List)
}
var node = List.node(10, next: List.end)
将上述代码生成SIL文件:
swiftc -emit-sil main.swift | xcrun swift-demangle
通过断点查看汇编代码,确实执行了
swift_allocObject
枚举-Swift和OC混编
OC调用Swift的枚举
OC
只能调用Swift
中Int
类型枚举
@objc enum NoMean: Int{
case a
case b
case c
case d
}
通过
@objc
声明后,桥接文件中自动生成SWIFT_ENUM
typedef SWIFT_ENUM(NSInteger, NoMean, closed) {
NoMeanA = 0,
NoMeanB = 1,
NoMeanC = 2,
NoMeanD = 3,
};
在
OC
的LGTest.m
文件中可以直接调用
@implementation LGTest
- (void)test{
NoMean a = NoMeanA;
}
@end
如果
enum
不声明类型,同时使用@objc
修饰,编译报错
如果
enum
声明String
类型,同时使用@objc
修饰,编译报错
Swift调用OC的枚举
OC
的LGTest.h
中,使用typedef NS_ENUM
声明枚举
typedef NS_ENUM(NSInteger, CEnum) {
CEnumInvalid = 0,
CEnumA = 1,
CEnumB,
CEnumC
};
在桥接文件中,自动生成
enum CEnum
public enum CEnum : Int {
case invalid = 0
case A = 1
case B = 2
case C = 3
}
OC
的LGTest.h
中,使用typedef enum
声明枚举
typedef enum{
Num1,
Num2
} OCNum;
在桥接文件中,自动生成
struct OCNum
,变成了结构体,并遵循了Equatable
和RawRepresentable
协议
public struct OCNum : Equatable, RawRepresentable {
public init(_ rawValue: UInt32)
public init(rawValue: UInt32)
public var rawValue: UInt32
}
在
Swift
的main.swift
文件中可以直接调用
let a: CEnum = .A
let b: OCNum = OCNum.init(rawValue: 1)
print("CEnum:\(a.rawValue)")
print("OCNum:\(b.rawValue)")
//输出以下内容:
//CEnum:1
//OCNum:1
内存对齐 & 字节对齐
- 内存对齐:
iOS
采用8字节
对齐方式,只会在对象初始化分配内存时出现。例如malloc
、calloc
- 字节对齐:第一个数据成员放在
offset
为0
的位置,以后每个数据成员存储的起始位置要从该成员大小或该成员的子成员大小(只要该成员有子成员,比如数组、结构体等)的整数倍开始。比如Int
为8字节
,则要从8
的整数倍地址开始存储
enum Shape {
case circle(radius: Double)
case rectangle(width: Int8, height: Int, w: Int16, h: Int32)
}
print("stride:\(MemoryLayout.stride)")
print("size:\(MemoryLayout.size)")
//输出以下内容:
//stride:24
//size:24
width
:Int8
类型,占1字节
height
:Int
类型,占8字节
w
:Int16
类型,占2字节
h
:Int32
类型,占4字节
width
占1字节
,但第二成员height
占8字节
。按字节对齐规则,height
起始位置必须是自身的整数倍,所以width
要补齐到8字节
。而w
占2字节
,但h
占4字节
,所以同理w
要补齐到4字节
。最终size
大小:8 + 8 + 4 + 4 = 24字节