谈谈Swift中的枚举内存布局

2,756 阅读4分钟

在掘金上看到从 汇编 到 Swift 枚举内存 的惊鸿一瞥之后,作者分析了几种不同枚举的内存布局,但是我感觉覆盖的不够全面,算是对作者那篇文章的一个补充。建议先看下作者的文章,作者的结论如下:

关联值枚举: 最大字节数之和 额外 + 1 最后一个字节 存放 case 类型 非关联值枚举: 内存 占用 1个字节 内存中 以下标数 为值,依次累加

疑问

不知道你看完之后,有没有我同样的疑问?

  1. 普通枚举时,内存占用一个字节,而一个字节最多只能从0到255,那么当case的选项超出256个时,会怎样
  2. 若关联值得类型是协议,结构体,类或其他枚举呢?这个时候内存占用是怎么样的
  3. 如果是递归枚举呢?

答案

  • 普通枚举,测试代码和结果如下说明测试代码中的show函数会打印,枚举的地址,内存和大小,从复制Mems
func test(){
     enum TestEnum {
       case testCase1
       case testCase2
    }
   var testEnum = TestEnum.testCase1
   show(val: &testEnum)
   testEnum = .testCase2
   show(val: &testEnum)
}

  • 当case选项过多超出256个时,比如出现300个时,会占用2个字节,由于超出2个字节需要的case太多,我没有进行测试,但应该是依次类推的
//测试case过多时
func test1(){
    var testEnum = MoreCaseEnum.case257
    show(val: &testEnum)
}

  • 当关联值是结构体时,跟作者的结论一样
struct TestStruct: TestProtocol {
    var testPropetry1 = 10
    var testPropetry2 = 11
    var testPropetry3 = 12
    var testPropetry4 = 13
    var testPropetry5 = 14
}
func test2() {
    enum TestStructEnum {
        case testCase1
        case testCase2(TestStruct)
        case testCase3
    }
    var testEnum = TestStructEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(TestStruct())
    show(val: &testEnum)
    testEnum = .testCase3
    show(val: &testEnum)
}

  • 当关联值是class时,跟作者的结论不一样,测试代码和结果如下 结论:枚举一共占用了8个字节,若是关联class的case,则存放对象的地址,其他的按照case的顺序赋值,此时是按照2*index赋值的,index为第几个无关联值的case
//测试关联值的类型是class
func test3() {
    enum TestClassEnum {
        case testCase1
        case testCase2(TestClass)
        case testCase3
    }
    var testEnum = TestClassEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(TestClass())
    show(val: &testEnum)
    testEnum = .testCase3
    show(val: &testEnum)
}

  • 当关联值的类型class+bool(这里换成其他小于4个字节l的类型都一样,比如Int16,Int8)时 结论:枚举占用8字节,当关联值是对象是,存放的是对象的地址,否则,8字节的前半部分存放的是区分类型,后半部分存放的关联的值或者枚举的case的位置(具体的规则我没测出来)
func test4() {
    enum TestClassOtherEnum {
        case testCase1
        case testCase2(TestClass)
        case testCase3(Bool)
    }
    var testEnum = TestClassOtherEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(TestClass())
    show(val: &testEnum)
    testEnum = .testCase3(true)
    show(val: &testEnum)
}

  • 关联值的类型是占用一字节的类型时,比如bool和其他无关联值枚举
    结论:枚举占用一个字节,前4位区分类型,后四位来表示具体的值
func test5() {
    enum TestEnum {
        case testCase1
        case testCase2
    }
    enum TestSamllEnum {
        case testCase1
        case testCase2(TestEnum)
        case testCase3(Bool)
    }
    var testEnum = TestSamllEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(.testCase2)
    show(val: &testEnum)
    testEnum = .testCase3(true)
    show(val: &testEnum)
}

  • 关联值的类型是协议时 结论:枚举占用40个字节,最后一项是区分类型,对于关联值协议的case,若满足协议的是class时,第一项是class的地址,若满足协议的是struct时,当struct的占用空间不大于24时,则前三项存放的是结构体的值,否则把结构体的值存放到外部
func test6() {
    enum TestProtocolEnum {
        case testCase1
        case testCase2(TestProtocol)
        case testCase3
    }
    var testEnum = TestProtocolEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(TestClass())
    show(val: &testEnum)
    testEnum = .testCase2(TestStruct())
    show(val: &testEnum)
    testEnum = .testCase3
    show(val: &testEnum)
}

  • 枚举类型是递归枚举时 结论:此时占用空间一直是8
func test7() {
    indirect enum TestIndirectEnum {
        case testCase1
        case testCase2(TestIndirectEnum)
        case testCase3
    }
    var testEnum = TestIndirectEnum.testCase1
    show(val: &testEnum)
    testEnum = .testCase2(.testCase3)
    show(val: &testEnum)
    testEnum = .testCase3
    show(val: &testEnum)
}

Other

以上所有的结论都是测试并总结出来,不能保证绝对的正确性,仅供参考,测试demo

参考链接