枚舉的基本用法
enum Direction {
case north
case south
case east
case west
}
// 多個成員值可以出現(xiàn)在同一行中挤悉,要用逗號隔開:
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 會暴露一個包含對應(yīng)枚舉類型所有情況的集合名為 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
關(guān)聯(lián)值(Associated Values)
- 有時將枚舉的成員值跟其他類型的值關(guān)聯(lián)存儲在一起尚氛,會非常有用
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
關(guān)聯(lián)值舉例
原始值 (Raw Values)
- 枚舉成員可以使用相同類型的默認值預(yù)先對應(yīng)诀诊,這個默認值叫做:原始值
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
注意:原始值不占用枚舉變量
原始值與關(guān)聯(lián)值不同。原始值是當(dāng)你第一次定義枚舉的時候阅嘶,它們用來預(yù)先填充的值属瓣,正如上面的三個 ASCII 碼。特定枚舉成員的原始值是始終相同的讯柔。關(guān)聯(lián)值在你基于枚舉成員的其中之一創(chuàng)建新的常量或變量時設(shè)定抡蛙,并且在你每次這么做的時候這些關(guān)聯(lián)值可以是不同的。
隱式原始值 (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)
- 遞歸枚舉是擁有另一個枚舉作為枚舉成員關(guān)聯(lián)值的枚舉捏卓。當(dāng)編譯器操作遞歸枚舉時必須插入間接尋址層极祸。你可以在聲明枚舉成員之前使用 indirect關(guān)鍵字來明確它是遞歸的慈格。
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獲取數(shù)據(jù)類型占用的內(nèi)存大小
先看一下int占用多少個字節(jié) (c語言中用sizeof,swift用MemoryLayout)
var age = 10
MemoryLayout<Int>.size // 8 和平臺有關(guān)系 64位 Int 等價 Int64
- 分析一下枚舉到底占用多少內(nèi)存
enum Password {
case number(Int, Int, Int, Int)
case other
}
MemoryLayout<Password>.stride // 40, 分配占用的空間大小
MemoryLayout<Password>.size // 33, 實際用到的空間大小
MemoryLayout<Password>.alignment // 8, 對齊參數(shù)
var pwd = Password.number(9, 8, 6, 4) // 至少占用32
pwd = .other // other存儲到pwd中的時候遥金,不會再動態(tài)改變pwd的內(nèi)存
MemoryLayout.stride(ofValue: pwd) // 40
MemoryLayout.size(ofValue: pwd) // 33 (32 + 1)
MemoryLayout.alignment(ofValue: pwd) // 8
- 分析一下原始值占用多少內(nèi)存
enum Season : Int {
case spring, summer, autumn, winter
}
原始值不會存儲到內(nèi)存中浴捆,固定死的,一個字節(jié)就夠用了
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
關(guān)聯(lián)值的話稿械,允許自己傳值选泻, 要存儲到枚舉變量內(nèi)存里面
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
思考下面枚舉變量的內(nèi)存布局
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
結(jié)論:
1個字節(jié)存儲成員值
N個字節(jié)存儲關(guān)聯(lián)值(N取占用內(nèi)存最大的關(guān)聯(lián)值美莫,任何一個case的關(guān)聯(lián)值都公用這個N個字節(jié))
窺探內(nèi)存
- 打印當(dāng)前e页眯,Debug -> Debug Workflow -> View Memory 輸入Mems打印出來的地址
- 窺探內(nèi)存細節(jié)的小工具:https://github.com/CoderMJLee/Mems
進一步觀察下面枚舉的內(nèi)存布局
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語句底層又是如何實現(xiàn)的?
根據(jù)成員值那個字節(jié)來判斷屬于哪個枚舉,然后進行操作厢呵。