Swift枚举关联值的内存探究

enum Season {
   case Spring, Summer, Autumn, Winter
}
let s = Season.Spring

这是枚举最基础的用法,可是在swift中,对枚举的功能进行了增强,也就是关联值。git

关联值能够将额外信息附加到 enum case中,像下面这样子。github

enum Test {
    case test1(v1: Int, v2: Int, v3: Int)
    case test2(v1: Int, v2: Int)
    case test3(v1: Int)
    case test4
}
let t = Test.test1(v1: 1, v2: 2, v3: 3)
    
switch t {
case .test1(let v1, let v2, let v3):
    print(v1, v2, v3)
default:
    break
}
// 输出: 1 2 3

咱们能够看到,在咱们建立一个枚举值t的时候,设置他的选项为test1,同时能够关联3个Int类型的值,而后在switch中,咱们还能够把这3个Int值取出来进行使用。swift

咱们今天的主要任务就是探索一下有关联值的枚举类型,再底层的内存布局是什么样子的,这些值都是怎么储存的。xcode

在OC中咱们使用sizeOf此类方法,能够输出一个变量占用内存的大小,在swift中也有此类的工做类,那就是MemoryLayout。ide

print(MemoryLayout<Int>.size)// 实际使用内存大小
print(MemoryLayout<Int>.stride)//分配内存大小
print(MemoryLayout<Int>.alignment)//内存对齐参数

// 输出 8 8 8

上面的例子是只是简单的实例MemoryLayout的用法,这个咱们知道,在64位的系统中Int类型确实是占用8个字节(64位)。接下来咱们就看一下枚举的内存占用状况。工具

点击Xcode菜单栏中的Debug -> Debug Workflow -> View Memory,而后在下面红色框中输入变量的内存地址,就能够看到变量的内存使用状况。布局

使用swift后,从xcode无法直接打印变量的内存地址,这里咱们使用了github上的一个工具类(github连接)来帮助咱们输出变量的内存地址。spa

1.png

准备工做完成后,咱们先从最基础的枚举开始。设计

enum Season {
    case Spring, Summer, Autumn, Winter
}
print("实际占用:",MemoryLayout<Season>.size)
print("分配:",MemoryLayout<Season>.stride)
print("对齐参数:", MemoryLayout<Season>.alignment)
    
var s = Season.Spring
print("内存地址",Mems.ptr(ofVal: &s))
    
print("内存数据",Mems.memStr(ofVal: &s, alignment: .one))
    
s = Season.Summer
print("内存数据",Mems.memStr(ofVal: &s, alignment: .one))
    
s = Season.Autumn
print("内存数据",Mems.memStr(ofVal: &s, alignment: .one))
    
s = Season.Winter
print("内存数据",Mems.memStr(ofVal: &s, alignment: .one))

注:Mems.memStr能够直接打印内存数据,这样咱们就不用每次拿到地址再去工具中看了3d

实际占用: 1
分配: 1
对齐参数: 1
内存地址 0x00007ffee753f0f0
内存数据 0x00
内存数据 0x01
内存数据 0x02
内存数据 0x03

咱们能够看到这种普通的枚举类型,只占用一个字节。并且经过咱们对变量设置不一样的枚举值,打印的这一个字节的数据也是不一样的,其实也就是使用这一个字节经过设置不一样的数值来表示不一样的枚举值,这样的话其实能够至少储存0x00-0xFF共256个值。那若是超过256个case呢?其实我以为没有必要考虑这种状况,枚举原本设计出就是为了区分有限中状况,若是太多,就像200多个,那彻底能够使用Int来设置不一样的值了,就不必用枚举了,固然,若是您愿意探究一下的话也是能够的。

接下来咱们使用一个带关联值的枚举来看一下。

enum Test {
    case test1(v1: Int, v2: Int, v3: Int)
    case test2(v1: Int, v2: Int)
    case test3(v1: Int)
    case test4
}
    
print("实际占用:",MemoryLayout<Test>.size)
print("分配:",MemoryLayout<Test>.stride)
print("对齐参数:", MemoryLayout<Test>.alignment)
    
var t = Test.test1(v1: 1, v2: 2, v3: 3)
print("内存地址",Mems.ptr(ofVal: &t))
    
print("内存数据",Mems.memStr(ofVal: &t, alignment: .one))
    
t = Test.test2(v1: 4, v2: 5)
print("内存数据",Mems.memStr(ofVal: &t, alignment: .one))
    
t = Test.test3(v1: 6)
print("内存数据",Mems.memStr(ofVal: &t, alignment: .one))
    
t = Test.test4
print("内存数据",Mems.memStr(ofVal: &t, alignment: .one))

下面是输出, 为了能直观一下,我给插了几个换行

实际占用: 25
分配: 32
对齐参数: 8
内存地址 0x00007ffee0afe0d8
内存数据 
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00
内存数据 
0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x01 
0x00 0x00 0x00 0x00 0x00 0x00 0x00
内存数据 
0x06 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x02 
0x00 0x00 0x00 0x00 0x00 0x00 0x00
内存数据 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x03 
0x00 0x00 0x00 0x00 0x00 0x00 0x00

实际占用了25个字节,咱们至少能够肯定,枚举的关联值是存储在枚举值的内存中的。

可是经过这一个例子其实可能还看不出有什么规律,你们能够多用几个例子来验证,这是我就直接说结论了。

有关联值得枚举实际占用的内存是最多关联值占用的内存+1,在咱们这个Test中,test1的关联值是最多的,有3个Int类型的关联值,因此要8*3=24字节来存放关联值,可是还须要一个字节来储存(辨别)是哪个case。

带着这个结论咱们看一下输出的结果:

当t=.test1时,前面24个字节分配给3个Int类型关联值,分别存储了1,2,3, 第25个字节是0。

当t=.test2时,前面24个字节仍是留给关联值的,可是test2只有两个关联值,因此使用了前面16个字节分配给他的关联值,此时17到24这8字节就空置,第25个字节是1。

...

最后当t = test4 , 没有关联值,因此前面的字节都是0, 只有第25个字节是3

以此类推...

第25个字节其实彻底能够当作一个辨识位,或者说第25个字节就是枚举的本质,经过不一样值来区分不一样case,只是由于有了关联值,因此开辟了更多的空间来存储而已。

后面多余的字节都是为了内存对齐,内存对齐相关的知识你们能够自行上网查阅。

补充:既然说到了关联值,那就顺便对枚举原始值说两句。具经过你打印带原始值的枚举的内存数据,发现是否带有原始值对枚举的内存占用并没有影响,因此原始值应该不是存储在枚举变量的内部的。你们能够本身试验一下

相关文章
相关标签/搜索