关于swift:Swift枚举关联值的内存探究

4次阅读

共计 3468 个字符,预计需要花费 9 分钟才能阅读完成。

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

这是枚举最根底的用法,然而在 swift 中,对枚举的性能进行了增强,也就是关联值。

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


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 值取出来进行应用。

咱们明天的次要工作就是摸索一下有关联值的枚举类型,再底层的内存布局是什么样子的,这些值都是怎么贮存的。

在 OC 中咱们应用 sizeOf 此类办法,能够输入一个变量占用内存的大小,在 swift 中也有此类的工作类,那就是 MemoryLayout。

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 链接) 来帮忙咱们输入变量的内存地址。

筹备工作实现后,咱们先从最根底的枚举开始。


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 能够间接打印内存数据,这样咱们就不必每次拿到地址再去工具中看了

理论占用: 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,只是因为有了关联值,所以开拓了更多的空间来存储而已。

前面多余的字节都是为了内存对齐,内存对其相干的常识大家能够自行上网查阅。

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

正文完
 0