swift学习笔记 ③ —— 枚举

Swift学习笔记 - 文集

语法篇

枚举

Swift 的枚举类似于 Objective C 和 C 的结构,使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内。例如我们定义以下表示星期的枚举:

// 定义枚举
enum DaysofaWeek {
    case Sunday
    case Monday
    case TUESDAY
    case WEDNESDAY
    case THURSDAY
    case FRIDAY
    case Saturday
}

var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY
switch weekDay {
case .Sunday:
    print("星期日")
case .Monday:
    print("星期一")
case .TUESDAY:
    print("星期二")
case .WEDNESDAY:
    print("星期三")
case .THURSDAY:
    print("星期四")
case .FRIDAY:
    print("星期五")
case .Saturday:
    print("星期六")
} // 打印输出 星期四

枚举中定义的值(如 Sunday,Monday,……和Saturday)是这个枚举的成员值(或成员)。case 关键词表示一行新的成员值将被定义。

在使用枚举时,一旦变量的类型被确定为枚举类型后,就可以使用一个缩写语法为变量设置另一个枚举值,例如上面代码中:

weekDay = .THURSDAY 

注意: 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的DaysofaWeek例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek类型。

枚举可分为关联值与原始值。

关联值

有时为了方便,可以将枚举的成员值和其他类型的值关联存储在一起,例如我们定义一个名为 Student 的枚举类型,它可以是 String 类型的 Score 和一个 Int 类型的 Grade:

enum Student{
    case score(Int)
    case grade(String)
}

在使用时,我们可以分别进行赋值:

var studentScore = Student.score(98)
var studentGrade = Student.grade("A")
switch studentScore {
case .score(let studScore):
    print("学生的成绩是: \(studScore)")
case .grade(let studGrade):
    print("学生的成绩是: \(studGrade)")
} // 打印输出 学生的成绩是 98

原始值 Raw Values

枚举成员可以使用相同类型的默认值预先对应,这个默认值就是原始值。
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
例如:

enum Grade : String {
    case perfect = "A"
    case great = "B"
    case good = "C"
    case bad = "D"
          
}
print(Grade. perfect.rawValue) // A
print(Grade. great.rawValue) // B
print(Grade. good.rawValue) // C
print(Grade. bad.rawValue) // D

隐式原始值

如果枚举的原始值类型是 IntString,Swift会自动分配原始值。例如:

enum Direction : String {
    case north = "north"
    case south = "south"
    case east = "east"
    case west = "west"
}
print(Direction.north) // north
print(Direction.north.rawValue) //north

上面例子中的枚举等价于:

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

再例如:

enum Month: Int {
    case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)月") // 打印结果为 数字月份为: 5月

枚举变量的内存布局

我们可以使用 MemoryLayout 类获取数据类型占用的内存大小。这里给大家介绍三种具体用法

  • MemoryLayout.stride(ofValue:) 系统分配的内存空间大小
  • MemoryLayout.size(ofValue:) 实际占用内存空间大小
  • MemoryLayout.alignment(ofValue:) 内存对齐参数

例如 Int 类型变量占据 8 个字节大小,所以内存分配了 8 个字节的内存,内存对齐参数也是 8 个字节。

var age = 10
MemoryLayout.stride(ofValue: age)    // 8
MemoryLayout.size(ofValue: age)    // 8
MemoryLayout.alignment(ofValue: age)    // 8

接下来我们声明几个不同的枚举来分析一下枚举类型的内存布局:

enum enumTest1 {
    case test1, test2, test3, test4
}
print(MemoryLayout.stride) // 1
print(MemoryLayout.size)  // 1
print(MemoryLayout.alignment)  // 1

enum enumTest2 : Int{
    case test1 = 5, test2, test3, test4 
}
print(MemoryLayout.stride)  // 1
print(MemoryLayout.size)  // 1
print(MemoryLayout.alignment)  // 1

enum enumTest3 {
    case test1(Int, Int, Int)
    case test2(Int, Int)
    case test3(Int)
    case test4(Bool)
    case test5
}
print(MemoryLayout.stride)  // 32
print(MemoryLayout.size)  // 25
print(MemoryLayout.alignment)  // 8

例子中,enumTest2设置了原始值enumTest3设置了关联值,但是通过打印我们可以看到,enumTest1enumTest2所占据的内存空间为 1 个字节,而enumTest3却占了 32 个字节的内存。这是为什么呢?我们可以通过 Xcode 自带的 View Memory 来具体查看一下三个枚举都保存了什么数据。

Xcode 自带的View Memory查看路径是,在进入断点后,工具栏 Debug -> Debug Workflow -> View Memory

Debug Memory.png

进入View Memory后,输入要查看对象的内存地址即可:

swift学习笔记 ③ —— 枚举_第1张图片
输入地址.png

下面我们分别测试打印三个枚举的地址,来具体分析每个枚举内部保存的数据。打印对象地址使用了一个第三方框架 Mems 。

var enu1 = enumTest1.test1
// var enu1 = enumTest1.test2
// var enu1 = enumTest1.test3
// var enu1 = enumTest1.test4
print(Mems.ptr(ofVal: &enu1))

我们将enumTest1的四个枚举值分别赋给enu1,然后查看enu1的内存地址,分别进入View Memory分析:

swift学习笔记 ③ —— 枚举_第2张图片
enu1-test1.png

swift学习笔记 ③ —— 枚举_第3张图片
enu1-test2.png
swift学习笔记 ③ —— 枚举_第4张图片
enu1-test3.png
swift学习笔记 ③ —— 枚举_第5张图片
enu1-test4.png

上文讲到,枚举enumTest1在内存中占 1 个字节的内存。通过测试我们可以看到,在给enu1赋不同的枚举值时,在内存中所占据的这 1 个字节保存着不同的数据。而且我们用同样的方式测试枚举enumTest2,发现内存显示的结果是相同的。

实际上,一般简单枚举和带原始值的枚举,在内存中保存的,仅仅是保存了 0、1、2、3......这些case的下标。即便是带原始值的枚举(enumTest2),内存中保存的并不是原始值。

那么带关联值的枚举呢?我们在上文已经测试打印了enumTest3的内存,具体是系统分配的内存空间为 32 个字节,实际占用内存空间为 25 个字节,内存对齐参数为 8 个字节。也就是说最后面 7 个字节是为了内存对齐增加的。

我们下面来测试一下enumTest3的内存布局:

var enu3 = enumTest3.test1(2, 3, 5)
// var enu3 = enumTest3.test2(6, 7)
// var enu3 = enumTest3.test3(8)
// var enu3 = enumTest3.test4(true)
// var enu3 = enumTest3.test5
print(Mems.ptr(ofVal: &enu3))

我们来看一下具体的内存布局:


swift学习笔记 ③ —— 枚举_第6张图片
enu3-test1.png

test1设置了三个Int类型关联值,我们可以将内存布局简化成一下:

02 00 00 00 00 00 00 00 
03 00 00 00 00 00 00 00 
05 00 00 00 00 00 00 00 
00                      // 以上25个字节为实际占用内存
00 00 00 00 00 00 00    // 此处7个字节为内存对齐增加的字节

我们将关联值设置为2、3、5,在内存中可以看到,最前面 8 个字节保存了第一个关联值 2 ,紧接着后面的 8 个字节保存了第二个关联值 3,再往后的 8 个字节保存了第三个关联值 5。第 25 个字节保存了 00

下面我们分析下一个case,即test2的内存布局:

var enu3 = enumTest3.test2(6, 7)
print(Mems.ptr(ofVal: &enu3))
swift学习笔记 ③ —— 枚举_第7张图片
enu3-test2.png

test2设置了两个Int类型关联值,我们可以将内存布局简化成一下:

06 00 00 00 00 00 00 00 
07 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
01                      // 以上25个字节为实际占用内存
00 00 00 00 00 00 00    // 此处7个字节为内存对齐增加的字节

我们将关联值设置为6、7,在内存中可以看到,最前面 8 个字节保存了第一个关联值 6 ,紧接着后面的 8 个字节保存了第二个关联值 7,再往后的 8 个字节没有保存数据。第 25 个字节保存了 01

下面我们分析下一个case,即test3的内存布局:

var enu3 = enumTest3.test3(8)
print(Mems.ptr(ofVal: &enu3))
swift学习笔记 ③ —— 枚举_第8张图片
enu3-test3.png

test3设置了一个Int类型关联值,我们可以将内存布局简化成一下:

08 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
02                      // 以上25个字节为实际占用内存
00 00 00 00 00 00 00    // 此处7个字节为内存对齐增加的字节

我们将关联值设置为8,在内存中可以看到,最前面 8 个字节保存了第一个关联值 8 ,紧接着后面的两个 8 个字节内存空间都没有保存数据。第 25 个字节保存了 02

下面我们分析下一个case,即test4的内存布局:

var enu3 = enumTest3.test4(true)
print(Mems.ptr(ofVal: &enu3))
swift学习笔记 ③ —— 枚举_第9张图片
enu3-test4.png

test4设置了一个Bool类型关联值true,我们可以将内存布局简化成一下:

01 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
03                      // 以上25个字节为实际占用内存
00 00 00 00 00 00 00    // 此处7个字节为内存对齐增加的字节

我们将关联值设置为true,在内存中可以看到,最前面 8 个字节保存了第一个关联值 01 ,紧接着后面的两个 8 个字节内存空间都没有保存数据。第 25 个字节保存了 03

下面我们分析下一个case,即test5的内存布局:

var enu3 = enumTest3.test5
print(Mems.ptr(ofVal: &enu3))
swift学习笔记 ③ —— 枚举_第10张图片
enu3-test5.png

test5没有关联值,我们可以将内存布局简化成一下:

00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 
04                      // 以上25个字节为实际占用内存
00 00 00 00 00 00 00    // 此处7个字节为内存对齐增加的字节

我们在内存中可以看到,最前面 24 个字节内存空间都没有保存数据。第 25 个字节保存了 04

通过上面一系列的验证,我们可以得出结论,在带有关联值的枚举中,内存中会将关联值保存起来,并且也会将枚举成员值的下标保存。而且枚举所占的内存空间大小,是根据关联值数量最多的成员值的内存 + 1。这是此枚举所占据的实际内存空间大小。系统会根据内存对齐原则,给枚举分配合适的内存空间。

结语

对枚举以及枚举的内存布局分析今天告一段落,后续在学习过程中如有补充会持续更新文章。文内如有不正确的地方,欢迎大家留言斧正。

更多技术知识请扫码关注微信公众号

iOS进阶

swift学习笔记 ③ —— 枚举_第11张图片
iOS进阶.jpg

你可能感兴趣的:(swift学习笔记 ③ —— 枚举)