简介
枚举是一种数据类型,只包含自定义的特定数据,它是一组有共同特性的数据的集合。
语法
Swift 中使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
enum enumname {
// 枚举成员
}
下面两种写法等价
enum Direction {
case east
case south
case west
case north
}
等价于
enum Direction {
case east, south, west, north
}
测试代码:
var direction = Direction.east
direction = .south // direction为已知类型,再次赋值可以省略枚举名
switch direction {
case .east:
print("东")
case .south:
print("南")
case .west:
print("西")
case .north:
print("北")
}
分类
关联值(相关值)
枚举的成员值和其它类型的值存储在一起。
下面我们定义一个名为 Student 的枚举类型,它可以是 Name 的一个字符串相关值(String),或者是 number 的一个相关值(Int)
enum Student {
case name(String)
case number(Int)
}
var s = Student.name("张三")
s = .number(20081024)
switch s {
case let .name(i):
print("名字:\(i)")
case let .number(i):
print("学号:\(i)")
}
输出:
学号:20081024
上面我们可以看到,枚举变量需要通过不同枚举成员传入的值,而这个值就是关联值(如,张三、20081024)。
不难发现:
传入的关联值都会存储在枚举变量的内存中,所以针对于关联值的枚举,其内存和将来存储的关联值类型相关的。
原始值
枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值
注意其定义: 在枚举名称后面有 : [数据类型(此处是String)],这里并不表示继承,而是原始值类型的枚举的定义格式,表示其原始值的类型。
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
上面定义一个学生成绩的等级,原始值分别是 "A"、"B"、"C"、"D"。
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
当然,在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
如果枚举的原始值类型是Int、String,Swift会自动分配原始值,即:隐式原始值
隐式原始值
原始值类型为 String
enum Direction : String {
case east = "east"
case south = "south"
case west = "west"
case north = "north"
}
等价于
enum Direction : String {
case east, south, west, north
}
测试代码
print(Direction.north) // north
print(Direction.north.rawValue) // north
原始值类型为 Int
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
可以看到:当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
当然,你可以手动指定枚举成员的原始值
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
注意:原始值不占用枚举变量的内存。
原始值:固定死的,和枚举成员永远绑定在一起,不允许后续绑定新值,而不是存 储在每一个枚举变量内存中,也就不会占用每一个枚举变量的内存。类似于标序号
枚举内存布局
无原始值和无关联值枚举的内存布局
定义枚举 EnumTest
enum EnumTest {
case e1, e2, e3
}
分别打印 EnumTest 实际占用的内存、分配的内存、和内存对齐值
print("实际占用:", MemoryLayout<EnumTest>.size)
print("实际分配:", MemoryLayout<EnumTest>.stride)
print("内存对齐:", MemoryLayout<EnumTest>.alignment)
输出:
实际占用: 1
实际分配: 1
内存对齐: 1
既没有原始值,又没有关联值的枚举类型,只占用一个字节。
通过内存视图查看这一个字节里面存储的具体值: Swift 没法直接通过 Xcode 打印枚举变量的内存地址,借用 MJLee 写的一个小工具:Mems
添加测试代码,断点调试
输出信息可以看到变量 e 的内存地址为:0x0000000100007710
查看内存视图,输入 e = .e1 的内存信息:
断点下一个,可以看到 e = .e2 这个字节的内存值是 1
同理,e = .e3 的那个字节值为 2
所以:在没有原始值和关联值的枚举,枚举占用一个字节的内存大小(枚举成员不超过256个的情况下),该字节的存储的值为依次存储 枚举成员值,即:分别为0、1、2、3...
特殊情况(这种情况在开发中没有存在的意义) 只有一个成员的枚举
enum Person {
case person
}
print("实际占用:", MemoryLayout<Person>.size)
print("实际分配:", MemoryLayout<Person>.stride)
print("内存对齐:", MemoryLayout<Person>.alignment)
输出信息:
实际占用: 0
实际分配: 1
内存对齐: 1
可以看到虽然分配了一个字节,但实际上没有使用,只有一个成员,就没有必要存储成员对应的值。
总结:在没有原始值和关联值的枚举,枚举占用一个字节的内存大小(枚举成员不超过256个的情况下),该字节的存储的值为依次存储 枚举成员值(0,1,2..)
关联值枚举的内存布局
定义 Time 枚举类型
enum Time {
case hour(Int, Int, Int)
case day(Int)
case moth(Int, Int)
case unknown(Bool)
}
查看 Time 实际占用的内存、分配的内存、和内存对齐值
print("实际占用:", MemoryLayout<Time>.size)
print("实际分配:", MemoryLayout<Time>.stride)
print("内存对齐:", MemoryLayout<Time>.alignment)
实际占用: 25
实际分配: 32
内存对齐: 8
同样内存视图查看该枚举变量实际适用的 25 个字节的内存中的具体值
var time = Time.hour(9, 10, 11)
print(Mems.ptr(ofVal: &time))
time = .day(1)
time = .moth(2, 3)
time = .unknown(true)
time 变量的内存地址:0x00000001000076f0
可以看到
var time = Time.hour(9, 10, 11) 时,三个值分别存储在对应的 8 字节中(09、0A、0B),最后 8 字节(实际只使用了其中的一个字节)存储的值为 00
下一步断点,即 time = .day(1) 时,
time = .day(1)
01 00 00 00 00 00 00 00 -- 存储关联值 1
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
01 00 00 00 00 00 00 00 -- 存储成员值,值为1
time = .moth(2, 3) 时,
time = .moth(2, 3)
02 00 00 00 00 00 00 00 -- 存储关联值 2
03 00 00 00 00 00 00 00 -- 存储关联值 3
00 00 00 00 00 00 00 00
02 00 00 00 00 00 00 00 -- 存储成员值,值为2
time = .unknown(true) 时
time = .unknown(true)
01 00 00 00 00 00 00 00 -- 存储关联值 1,true为1,false为0
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00 -- 存储成员值,值为3
可以发现,关联值枚举类型,会分配内存空间用于存储成员值,同时分配一部分内存空间存储关联值。
总结:关联值枚举所占空间:‘1个字节存储成员值’ + ‘N个字节存储关联值’。
1)其中 N 为占用内存最大的关联值,上面的例子为 case hour(Int, Int, Int),8 * 3 = 24 字节;
2)任何一个 case 的关联值都共用这 N 个字节。
原始值枚举的内存布局
创建 Sex 枚举类型,并添加测试代码,进行断点调试
实际占用: 1
实际分配: 1
内存对齐: 1
0x0000000100007718
sex = Sex.male 所占用的一字节中存储的值为:0
sex = .female 所占用的一字节中存储的值为:1
sex = .renyao 所占用的一字节中存储的值为:2
可以看出来,原始值枚举,其原始值并不占用枚举的内存空间,而只是分配了一个字节用于存储成员值。
总结:原始值枚举只会分配相应的内存空间(不超过256个成员的情况下分配一个字节)用于枚举成员值,而不会针对原始值分配空间。