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
隐式原始值
如果枚举的原始值类型是 Int
、String
,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
设置了关联值,但是通过打印我们可以看到,enumTest1
和enumTest2
所占据的内存空间为 1 个字节,而enumTest3
却占了 32 个字节的内存。这是为什么呢?我们可以通过 Xcode 自带的 View Memory
来具体查看一下三个枚举都保存了什么数据。
Xcode 自带的View Memory
查看路径是,在进入断点后,工具栏 Debug
-> Debug Workflow
-> View Memory
。
进入View Memory
后,输入要查看对象的内存地址即可:
下面我们分别测试打印三个枚举的地址,来具体分析每个枚举内部保存的数据。打印对象地址使用了一个第三方框架 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
分析:
上文讲到,枚举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))
我们来看一下具体的内存布局:
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))
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))
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))
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))
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进阶