Swift 枚举-从汇编角度看枚举内存结构

时间:2024-01-26 11:55:20

一、基本使用

先看枚举的几种使用(暂不要问,看看是否都能看懂,待会会逐一讲解)

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个字节

 

 拓展-大小端问题

 

 以上就是枚举内存的底层结构,希望对大家有所帮助!!! 下一篇将讲述struct与class的区别!