- Swift Enum-细说枚举
- Swift Enum-内存初探
用过Swift enum的同学肯定已经被他的强壮所招引, 今日咱们不专门解说Swift enum的高级用法, 而是对 enum在内存中存储的巨细进行一次深化探求
0. 准备工作
-
- MemoryLayout
MemoryLayout能够帮咱们核算类型在内存中所占用的空间
MemoryLayout.size // 占用的内存巨细 MemoryLayout.stride // 分配的内存巨细 MemoryLayout.alignment // 内存的对齐巨细
-
- withUnsafePointer()
withUnsafePointer()办法能够获取swift中目标的指针地址
-
- Swift编译器中心码SIL
SIL是 Swift编译器发生的中心产物, 通过SIL咱们能够简略的分析代码的履行进程
引荐一篇大佬的解说: /post/684490…
1. 探求一般的enum
enum Color {
case red
case yellow
case blue
}
func test() {
var c1 = Color.red
print(MemoryLayout.size(ofValue: c1)) // 1
var c2 = Color.yellow
print(MemoryLayout.size(ofValue: c2)) // 1
var c3 = Color.blue
print(MemoryLayout.size(ofValue: c3)) // 1
}
咱们能够看到, 关于一个普一般通的 enum, 他在内存中只占用一个字节, 甚至在内存中他存储的仅仅是0, 1, 2…..
unsigned numTagBytes = (numTags <= 1 ? 0 :
numTags < 256 ? 1 :
numTags < 65536 ? 2 : 4);
在enum的源码中咱们找到了一些线索, 一个枚举所占的字节数 numTagBytes 跟case的个数有关, 假如你问为什么只要 0,1,2,4 ? 我想假如一个枚举的case 6.5w都不够的话, 我会送你一个 “???”
关于一般的枚举, 咱们只需求一个字节就能够记录枚举详细表明哪一个值.
2. 探求带有原始值的enum
enum Color2: String {
case red = "redColor"
case yellow = "yellowColor"
case blue = "blueColor"
}
func test() {
var c1 = Color2.red
print(MemoryLayout.size(ofValue: c1)) // 1
var c2 = Color2.yellow
print(MemoryLayout.size(ofValue: c2)) // 1
var c3 = Color2.blue
print(MemoryLayout.size(ofValue: c3)) // 1
}
能够看到与一般的enum相同, 带有原始值的enum相同只占一个字节的空间, 而且存储的相同是0,1,2 也便是标志位, 那么咱们是如何拿到enum的rawValue呢? 这儿就需求凭借sil了
/**
......
// function_ref Color2.rawValue.getter
%5 = function_ref @$s14ViewController6Color2O8rawValueSSvg : $@convention(method) (Color2) -> @owned String // user: %6
......
// Color2.rawValue.getter
sil hidden @$s14ViewController6Color2O8rawValueSSvg : $@convention(method) (Color2) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $Color2):
switch_enum %0 : $Color2, case #Color2.red!enumelt: bb1, case #Color2.yellow!enumelt: bb2, case #Color2.blue!enumelt: bb3 // id: %2
bb1: // Preds: bb0
%3 = string_literal utf8 "redColor" // user: %8
......
br bb4(%8 : $String) // id: %9
bb2: // Preds: bb0
%10 = string_literal utf8 "yellowColor" // user: %15
......
br bb4(%15 : $String) // id: %16
bb3: // Preds: bb0
%17 = string_literal utf8 "blueColor" // user: %22
......
br bb4(%22 : $String) // id: %23
// %24 // user: %25
bb4(%24 : $String): // Preds: bb3 bb2 bb1
return %24 : $String // id: %25
} // end sil function '$s14ViewController6Color2O8rawValueSSvg'
*/
从sil中, 咱们能够看到 function_ref Color2.rawValue.getter
这样一句代码, 也便是说 rawValue其实是一个getter办法, 在详细的办法中, 使用switch_enum
对枚举进行了匹配, 而且履行了 bb1, bb2, bb3中的某一个, 而 bbn 则是直接返回了一个字符串罢了.
所以, 带有原始值的enum也是只占用一个字节的空间, 而且存储自己的标志位
3. 探求带有相关值的enum
enum Color3 {
case red
case green(val: Bool)
case yellow(val1: Int, val2: Int32)
case blue(val:Int)
}
func test() {
var c1 = Color3.red
print(MemoryLayout.size(ofValue: c1)) // 13
var c2 = Color3.green(val: true)
print(MemoryLayout.size(ofValue: c2)) // 13
var c3 = Color3.yellow(val1: 9, val2: 22)
print(MemoryLayout.size(ofValue: c3)) // 13
var c4 = Color3.blue(val: 14)
print(MemoryLayout.size(ofValue: c4)) // 13
}
能够看到, 带有相关值的enum, 占用了13个字节. 在图3中, 能够看到前12个字节存储了enum相关的详细值(赤色框), 最终一个字节存储的是enum的标志位. 这样的存储结构, 咱们就能够通过标志位判别详细是哪一个case, 通过前12个自己拿到相关值的详细值.
能够看出: 比如中相关值的枚举占用的内存巨细为: 8(存储Int) + 4(存储Int32) + 1(存储标志位),
那么咱们是否能够做出总结: 相关值的枚举占用的内存巨细 = (存储相关值最大case)需求的巨细 + 1 呢?
4. 探求带有相关值的enum(特别)
enum Color4 {
case red
case green(val: Bool)
case blue(val: Bool)
}
func test() {
var c1 = Color4.red
print(MemoryLayout.size(ofValue: c1)) // 1
var c2 = Color4.green(val: true)
print(MemoryLayout.size(ofValue: c2)) // 1
var c3 = Color4.blue(val: true)
print(MemoryLayout.size(ofValue: c3)) // 1
}
能够看到这次带有相关值的enum竟然也只占用一个字节. 可是仔细思考一下, 如同的确一个字节就够了. 如图4中, 红框的第一位(0,4,8) 表明每个case的标志位, 红框的第二位(0,1,1)表明相关Bool的值.
所以, 带有相关值的枚举详细占用空间还是需求详细情况详细分析
5. 探求enum的办法和核算特点
enum Color5 {
case red
case yellow
case blue
var intValue: Int {
return 20
}
func desc() -> String {
return "desc"
}
static func staticDesc() -> String {
return "staticDesc"
}
}
func test() {
var c = Color5.red
_ = c.desc() // "desc"
_ = Color5.staticDesc() // "staticDesc"
_ = c.intValue
}
/** Color5.desc()
// function_ref Color5.desc()
%5 = function_ref @$s14ViewController6Color4O4descSSyF : $@convention(method) (Color4) -> @owned String // user: %6
// Color5.desc()
sil hidden @$s14ViewController6Color4O4descSSyF : $@convention(method) (Color4) -> @owned String {
// %0 "self" // user: %1
bb0(%0 : $Color5):
%2 = string_literal utf8 "desc" // user: %7
.......
return %7 : $String // id: %8
} // end sil function '$s14ViewController6Color4O4descSSyF'
*/
/** Color5.staticDesc()
%8 = metatype $@thin Color5.Type // user: %10
// function_ref static Color5.staticDesc()
%9 = function_ref @$s14ViewController6Color4O10staticDescSSyFZ : $@convention(method) (@thin Color5.Type) -> @owned String // user: %10
// static Color5.staticDesc()
sil hidden @$s14ViewController6Color4O10staticDescSSyFZ : $@convention(method) (@thin Color5.Type) -> @owned String {
// %0 "self" // user: %1
bb0(%0 : $@thin Color5.Type):
%2 = string_literal utf8 "staticDesc" // user: %7
.........
return %7 : $String // id: %8
} // end sil function '$s14ViewController6Color4O10staticDescSSyFZ'
*/
/** Color5.intValue
// function_ref Color5.intValue.getter
%12 = function_ref @$s14ViewController6Color4O8intValueSivg : $@convention(method) (Color5) -> Int // user: %13
// Color5.intValue.getter
sil hidden @$s14ViewController6Color4O8intValueSivg : $@convention(method) (Color4) -> Int {
// %0 "self" // user: %1
bb0(%0 : $Color5):
%2 = integer_literal $Builtin.Int64, 20 // user: %3
......
return %3 : $Int // id: %4
} // end sil function '$s14ViewController6Color4O8intValueSivg'
*/
根据sil能够看到, 不管是一般办法还是static办法还是核算特点, 都是简略的办法调用, 这也便是为什么Swift 中enum能够声明办法和核算特点