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,只是由於有了關聯值,因此開闢了更多的空間來存儲而已。

後面多餘的字節都是爲了內存對齊,內存對齊相關的知識你們能夠自行上網查閱。

補充:既然說到了關聯值,那就順便對枚舉原始值說兩句。具經過你打印帶原始值的枚舉的內存數據,發現是否帶有原始值對枚舉的內存佔用並沒有影響,因此原始值應該不是存儲在枚舉變量的內部的。你們能夠本身試驗一下

相關文章
相關標籤/搜索