1. 程式人生 > >Swift 列舉-從彙編角度看列舉記憶體結構

Swift 列舉-從彙編角度看列舉記憶體結構

一、基本使用

先看列舉的幾種使用(暫不要問,看看是否都能看懂,待會會逐一講解)

1、操作一 簡單使用

//第一種方式
enum Direction {
    case east
    case west
    case south
    case north
    
    func testDir() -> String {
        switch self {
        case .east:
            return "東邊"
        case .west:
            return "西邊"
        case .south:
            return "南邊"
        case .north:
            return "北邊"
        }
    }
}

//第二種方式
enum Direction1 {
    case east, west, south, north
    
    func testDir() -> String {
        switch self {
        case .east:
            return "東邊"
        case .west:
            return "西邊"
        case .south:
            return "南邊"
        case .north:
            return "北邊"
        }
    }
}

var dir = Direction.east
dir = .north
var dir1 = Direction1.east
dir1 = .north

第一種和第二種完全一樣。

 

2、操作二 關聯值(Associated Values)

關聯值(Associated Values)將列舉的成員值跟其他型別的值關聯儲存在一起,非常有用!

2.1 關聯值示例1

enum Score {
    case points(Int)
    case grade(Character)
}
var score = Score.points(98)
score = .grade("A")

func testScore() {
    switch score {
    case let .points(i):
        print(i,"points")
    case let .grade(i):
        print("grade",i)
    }
}

2.2 關聯值示例2

enum Date {
    case digit(year: Int, month: Int, day: Int)
    case string(String)
}

var date = Date.digit(year: 2011, month: 9, day: 10)
date = .string("2011-09-10")

func testDate() {
    switch date {
    case .digit(let year, let month, let day):
        print(year, month, day)
    case let .string(value):
        print(value)
    }
}

2.3 關聯值示例3

手機密碼方式有以下兩種,可以用列舉表示,用關聯值表示,圖如下:

上面的圖可以用關聯值列舉表示如下

enum Password {
    case number(Int, Int, Int, Int)
    case gesture(String)
}

var pwd = Password.number(1, 3, 7, 8)
pwd = .gesture("1378")

func testPwd() {
    switch pwd {
    case let .number(n1, n2, n3, n4):
        print("number is ", n1, n2, n3, n4)
    case let .gesture(str):
        print("gesture is " ,str)
    }
}

 

3、操作三 原始值(Raw Values)

原始值: 列舉成員可以使用相同型別的預設值預先對應,預設值叫做原始值,原始值不佔用列舉變數的記憶體

enum PokerSuit: Character {
    case spade = "黑"
    case heart = "紅"
    case diamond = "方"
    case club = "花"
}

var suit = PokerSuit.spade
print(suit.rawValue)//黑
print(PokerSuit.club.rawValue)//花

 

4、隱式原始值(Implicitly Assigned Raw Values)

如果列舉的原始值是Int,String,Swift會自動分配原始值,和成員值一樣

4.1 隱式原始值示例1

Direction和Direction1意義是一樣的

enum Direction: String {
    case north = "north"
    case south = "south"
    case east  = "east"
    case west  = "west"
}

enum Direction1: String {
    case north, south, east, west
}

print(Direction.north) //north
print(Direction1.north.rawValue)// north

4.2 隱式原始值示例2

Int型別,預設為0開始

enum Season: Int {
    case spring, summer, autumn, winter
}
print(Season.spring.rawValue) // 0
print(Season.summer.rawValue) // 1
print(Season.autumn.rawValue) // 2
print(Season.winter.rawValue) //3

4.3 隱式原始值示例3

enum Season: Int {
    case spring = 1, summer, autumn = 4, winter
}
print(Season.spring.rawValue) // 1
print(Season.summer.rawValue) // 2
print(Season.autumn.rawValue) // 4
print(Season.winter.rawValue) //5

上面講述列舉的基本使用,下面我們將進入核心內容-從彙編的角度來看列舉的記憶體!!

 

二、彙編角度看列舉記憶體

示例1: 簡單實用-通過下面程式碼檢視列舉例項佔用多少記憶體位元組等

enum TestEnum {
    case test1, test2, test3
}

var t = TestEnum.test1
t = .test2
t = .test3

通過MemoryLayout檢視記憶體大小

enum TestEnum {
    case test1, test2, test3
}

var t = TestEnum.test1
t = .test2
t = .test3
print(MemoryLayout<TestEnum>.size) //TestEnum實際佔用記憶體空間
print(MemoryLayout<TestEnum>.stride)//系統分配給TestEnum的記憶體空間
print(MemoryLayout<TestEnum>.alignment)//對齊引數

執行結果如下

其實Swift還是很聰明的,僅僅使用一個位元組來判斷物件的不同,下面窺探test1,test2,test3的記憶體

因為Swift不支援列舉看底層的,所以通過一個記憶體訪問小工具檢視記憶體地址,然後通過記憶體地址檢視記憶體佈局

 

 拿到記憶體地址後,可以通過view memory檢視內容

 

將地址0x0000000100006660輸入進去

 

因為通過上面發現佔用一個位元組,所以看第一個位元組儲存的為00,t為test1時

將斷點向後移,看t = test2時,t的記憶體儲存的時

 

再次看下t = test2 記憶體儲存的值為

 

 最後看下t = test3記憶體儲存為

 

 這種形式的列舉定義形式佔用一個位元組,可以代表的列舉範圍也就是0x00-0xFF共256個case,足以表示所有情況的列舉窮舉啦!

從示例1中,當列舉裡面僅僅是case多個物件,列舉記憶體僅僅會分配1個位元組來儲存各個case,case對應的為0,1,2……

 

示例2 帶有原始值

enum TestEnum: Int {
    case test1 = 1, test2 = 2, test3 = 3
}
var t = TestEnum.test1
t = .test2
t = .test3

觀察上面帶有原始值列舉分配記憶體和佔用記憶體情況

 

 從最上面講述帶有原始值的列舉(紅色標記)原始值不佔用列舉變數的記憶體

所以僅僅需要1個位元組來區分test1, test2,test3,我們再來看一個test2,看記憶體儲存的是多少 

 

 看出test2儲存的是依然是1,和原始值內容沒有任何關係,儲存和示例1沒有區別,再次印證了,原始值不佔用列舉變數的記憶體,不影響列舉記憶體結構和儲存

 

示例3 帶有關聯值的列舉記憶體結構

關聯值從上面基本使用得出關聯值(Associated Values)將列舉的成員值跟其他型別的值關聯儲存在一起

enum TestEnum {
    case test1(Int, Int, Int)
    case test2(Int, Int)
    case test3(Int)
    case test4(Bool)
    case test5
}
var t = TestEnum.test1(1, 2, 3)
t = .test2(4, 5)
t = .test3(6)
t = .test4(true)
t = .test5

繼續使用MemoryLayout來看記憶體分配

 

從上面可看出TestEnum列舉實際佔用記憶體空間大小為25,又因為記憶體對齊為8,所以系統分配了32個位元組的大小給TestEnum

下面著重講解為什麼實際佔用了25個位元組,又是怎麼儲存的?

通過記憶體小工具檢視列舉地址

 

 然後View Memory工具檢視記憶體結構如下

 

 上面得出Int佔據8個位元組,對於TestEnum.test1(1, 2, 3)用24個位元組儲存這些關聯值,得出關聯值(Associated Values)將列舉的成員值跟其他型別的值關聯儲存在一的結論是正確的!

(拓展:為什麼01,02放在前面,為什麼不是放在後面,這牽扯到大小端的問題?下面講述)

下面看test2的儲存結構t = .test2(4, 5)

 

 看第25個位元組為Test2為0x01 = 1, test1的第25個位元組為0x00 = 0, 依次類推,檢視test4應該為3,下面揭開謎底

 

 關聯值列舉儲存結論

有一個位元組儲存成員值,用於區分哪一個成員值

N個位元組儲存關聯值(N取佔用記憶體量最大的關聯值),任何一個case的關聯值都會共用這N個位元組

 

上面程式碼與檢視記憶體小工具封裝程式碼https://github.com/zxy1829760/SwiftEnum

 拓展-大小端問題

 儲存0x11223344,大小端儲存如下

 以上就是列舉記憶體的底層結構,希望對大家有所幫助!!! 下一篇將講述struct與class的區別!

&n