枚举的基本用法
enum Direction {
case north
case south
case east
case west
}
// 多个成员值可以出现在同一行中,要用逗号隔开:
enum Direction {
case north, south, east, west
}
var dir = Direction.west
dir = Direction.east
dir = .north
print(dir) // north
使用 Switch 语句来匹配枚举值
switch dir {
case .north:
print("north")
case .south:
print("south")
case .east:
print("east")
case .west:
print("west")
}
遍历枚举情况(case)
对于某些枚举来说,如果能有一个集合包含了枚举的所有情况就好了。你可以通过在枚举名字后面写 : CaseIterable 来允许枚举被遍历。Swift 会暴露一个包含对应枚举类型所有情况的集合名为 allCases 。下面是例子:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
关联值(Associated Values)
- 有时将枚举的成员值跟其他类型的值关联存储在一起,会非常有用
enum Score {
case points(Int)
case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
switch score {
case let .points(i):
print(i, "points")
case let .grade(i):
print("grade", i)
}
// grade A
enum Date {
case digit(year: Int, month: Int, 用户 : Int)
case string(String)
}
var date = Date.digit(year: 2011, month: 9, day: 10)
date = .string("2011-09-10")
switch date {
case .digit(let year, let month, let day):
print(year, month, day)
case let .string(value):
print(value)
}
- 必要时let也可以改为var
关联值举例
原始值 (Raw Values)
- 枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值
enum PokerSuit : Character {
case spade = "♠"
case heart = "♥"
case diamond = "♦"
case club = "♣"
}
var suit = PokerSuit.spade
print(suit) // spade
print(suit.rawValue) // ♠
print(PokerSuit.club.rawValue) // ♣
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
注意:原始值不占用枚举变量
原始值与关联值不同。原始值是当你第一次定义枚举的时候,它们用来预先填充的值,正如上面的三个 ASCII 码。特定枚举成员的原始值是始终相同的。关联值在你基于枚举成员的其中之一创建新的常量或变量时设定,并且在你每次这么做的时候这些关联值可以是不同的。
隐式原始值 (Implicitly Assigned Raw Values)
- 如果枚举的原始值类型是Int、String,Swift会自动分配原始值
enum Direction : String {
case north = "north"
case south = "south"
case east = "east"
case west = "west"
}
// 等价于
enum Direction : String {
case north, south, east, west
}
print(Direction.north) // north
print(Direction.north.rawValue) // north
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
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
递归枚举 (Recursive Enumeration)
- 递归枚举是拥有另一个枚举作为枚举成员关联值的枚举。当编译器操作递归枚举时必须插入间接寻址层。你可以在声明枚举成员之前使用 indirect关键字来明确它是递归的。
indirect enum ArithExpr {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
enum ArithExpr {
case number(Int)
indirect case sum(ArithExpr, ArithExpr)
indirect case difference(ArithExpr, ArithExpr)
}
let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)
func calculate(_ expr: ArithExpr) -> Int {
switch expr {
case let .number(value):
return value
case let .sum(left, right):
return calculate(left) + calculate(right)
case let .difference(left, right):
return calculate(left) - calculate(right)
}
}
calculate(difference)
MemoryLayout
可以使用MemoryLayout获取数据类型占用的内存大小
先看一下int占用多少个字节 (c语言中用sizeof,swift用MemoryLayout)
var age = 10
MemoryLayout<Int>.size // 8 和平台有关系 64位 Int 等价 Int64
- 分析一下枚举到底占用多少内存
enum Password {
case number(Int, Int, Int, Int)
case other
}
MemoryLayout<Password>.stride // 40, 分配占用的空间大小
MemoryLayout<Password>.size // 33, 实际用到的空间大小
MemoryLayout<Password>.alignment // 8, 对齐参数
var pwd = Password.number(9, 8, 6, 4) // 至少占用32
pwd = .other // other存储到pwd中的时候,不会再动态改变pwd的内存
MemoryLayout.stride(ofValue: pwd) // 40
MemoryLayout.size(ofValue: pwd) // 33 (32 + 1)
MemoryLayout.alignment(ofValue: pwd) // 8
- 分析一下原始值占用多少内存
enum Season : Int {
case spring, summer, autumn, winter
}
原始值不会存储到内存中,固定死的,一个字节就够用了
enum Season : Int { // 原始值是int类型
// 0 1 2 3
case spring = 1, summer, autumn, winter
}
var s = Season.spring // 0
var s1 = Season.spring // 0
var s2 = Season.spring // 0
关联值的话,允许自己传值, 要存储到枚举变量内存里面
enum Password {
case number(Int, Int, Int, Int)
case other
}
var pwd1 = Password.number(22, 55, 789, 2030)
var pwd2 = Password.number(9, 8, 6, 4)
var pwd3 = Password.number(111, 222, 100, 40200)
MemoryLayout<Password>.stride
MemoryLayout<Password>.size
MemoryLayout<Password>.alignment
思考下面枚举变量的内存布局
enum TestEnum {
case test1, test2, test3
}
var t = TestEnum.test1
t = .test2
t = .test3
enum TestEnum : Int {
case test1 = 1, test2 = 2, test3 = 3
}
var t = TestEnum.test1
t = .test2
t = .test3
enum TestEnum {
case test
}
var t = TestEnum.test
// 就一个case,不需要分配就是它自己
enum TestEnum {
case test(Int)
}
var t = TestEnum.test(10)
enum TestEnum {
case test1(Int, Int, Int)
case test2(Int, Int)
case test3(Int)
case test4(Bool)
case test5
}
var e = TestEnum.test1(1, 2, 3)
// 小端模式:高高低低 (01 00 00 00 00 00 00 00 00 -> 00 00 00 00 00 00 00 00 01)
// 01 00 00 00 00 00 00 00 00
// 02 00 00 00 00 00 00 00 00
// 03 00 00 00 00 00 00 00 00
// 00
// 00 00 00 00 00 00 00 00
e = .test2(4, 5)
// 04 00 00 00 00 00 00 00 00
// 05 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00
// 01
// 00 00 00 00 00 00 00 00
e = .test3(6)
// 推测
// 06 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00
// 02
// 00 00 00 00 00 00 00 00
e = .test4(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 00 00 00
// 03
// 00 00 00 00 00 00 00 00
e = .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 00 00 00
// 04
// 00 00 00 00 00 00 00 00
结论:
1个字节存储成员值
N个字节存储关联值(N取占用内存最大的关联值,任何一个case的关联值都公用这个N个字节)
窥探内存
- 打印当前e,Debug -> Debug Workflow -> View Memory 输入Mems打印出来的地址
- 窥探内存细节的小工具:https://github.com/CoderMJLee/Mems
进一步观察下面枚举的内存布局
enum TestEnum {
case test0
case test1
case test2
case test4(Int)
case test5(Int, Int)
case test6(Int, Int, Int, Bool)
}
enum TestEnum {
case test0
case test1
case test2
case test4(Int)
case test5(Int, Int)
case test6(Int, Bool, Int)
}
enum TestEnum {
case test0
case test1
case test2
case test4(Int)
case test5(Int, Int)
case test6(Int, Int, Bool, Int)
}
它们的switch语句底层又是如何实现的?
根据成员值那个字节来判断属于哪个枚举,然后进行操作。