1. Swift Enum-细说枚举
  2. Swift Enum-内存初探

用过Swift enum的同学肯定已经被他的强壮所招引, 今日咱们不专门解说Swift enum的高级用法, 而是对 enum在内存中存储的巨细进行一次深化探求

0. 准备工作

    1. MemoryLayout

    MemoryLayout能够帮咱们核算类型在内存中所占用的空间

    MemoryLayout.size // 占用的内存巨细
    MemoryLayout.stride // 分配的内存巨细
    MemoryLayout.alignment // 内存的对齐巨细
    
    1. withUnsafePointer()

    withUnsafePointer()办法能够获取swift中目标的指针地址

    1. 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
}

Swift Enum-内存初探
咱们能够看到, 关于一个普一般通的 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
}

Swift Enum-内存初探

能够看到与一般的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
}

Swift Enum-内存初探

能够看到, 带有相关值的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
}

Swift Enum-内存初探
能够看到这次带有相关值的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能够声明办法和核算特点