本文主要介紹enum的常見使用形式,以及枚舉大小是如何計(jì)算的
補(bǔ)充:添加腳本自動(dòng)生成SIL
- 通過(guò)target -> +,選擇 other -> Aggregate,,然后命名為
CJLScript
image

- 選中CJLScript,選擇
Build Phases->New Run Script Phase
image
- 在
Run Script中輸入以下命令
swiftc -emit-sil ${SRCROOT}/06、EnumTest/main.swift | xcrun swift-demangle > ./main.sil && code main.sil
然后我們就可以通過(guò)腳本自動(dòng)生成SIL并自動(dòng)打開啦 ??ヽ(°▽°)ノ??
C中的枚舉
在介紹swift中的枚舉之前,首先我們來(lái)回顧下C中的枚舉寫法,如下所示
enum 枚舉名{
枚舉值1,
枚舉值2,
......
};
<!--舉例:表示一周7天-->
enum Week{
MON, TUE, WED, THU, FRI, SAT, SUN
};
<!--更改C中枚舉默認(rèn)值-->
//如果沒(méi)有設(shè)置枚舉默認(rèn)值,一般第一個(gè)枚舉成員的默認(rèn)值為整型0,后面依次遞推
enum Week{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
<!--C中定義一個(gè)枚舉變量-->
//表明創(chuàng)建了一個(gè)枚舉,并聲明了一個(gè)枚舉變量Week
enum Week{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}week;
//或者下面這種寫法,省略枚舉名稱
enum{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
}week;
Swift中的枚舉
在swift中,枚舉的創(chuàng)建方式如下所示,如果沒(méi)有指定枚舉值的類型,那么enum默認(rèn)枚舉值是整型的
<!--1、寫法一-->
enum Week{
case MON
case TUE
case WED
case THU
case FRI
case SAT
case SUN
}
<!--2、寫法二-->
//也可以直接一個(gè)case,然后使用逗號(hào)隔開
enum Week{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
<!--定義一個(gè)枚舉變量-->
var w: Week = .MON
- 如果此時(shí)想創(chuàng)建一個(gè)枚舉值是String類型的enum,可以通過(guò)指定enum的枚舉值的類型來(lái)創(chuàng)建,其中
枚舉值和原始值rawValue的關(guān)系為case 枚舉值 = rawValue原始值
/*
- =左邊的值是枚舉值,例如 MON
- =右邊的值在swift中稱為 RawValue(原始值),例如 "MON"
- 兩者的關(guān)系為:case 枚舉值 = rawValue原始值
*/
enum Week: String{
case MON = "MON"
case TUE = "TUE"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
- 如果不想寫枚舉值后的字符串,也可以使用
隱式RawValue分配,如下所示
<!--String類型-->
enum Week: String{
case MON, TUE, WED = "WED", THU, FRI, SAT, SUN
}
<!--Int類型-->
//MON是從0開始一次遞推,而WED往后是從10開始一次遞推
enum Week: Int{
case MON, TUE, WED = 10, THU, FRI, SAT, SUN
}
枚舉的訪問(wèn)
注:如果enum沒(méi)有聲明類型,是沒(méi)有
rawValue屬性的
image
枚舉的訪問(wèn)方式如下所示
enum Week: String{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
var w = Week.MON.rawValue
<!--訪問(wèn)-->
print(w)
<!--打印結(jié)果-->
MON
這里就有一個(gè)疑問(wèn),swift是如何做到打印 MON的?我們通過(guò)SIL文件分析
- 首先查看
SIL文件中的enum,底層多增加了一些東西1、給枚舉值的類型,通過(guò)
typealias取了一個(gè)別名RawValue2、默認(rèn)添加了一個(gè)
可選類型的init方法3、增加一個(gè)
計(jì)算屬性rawValue,用于獲取枚舉值的原始值

- 查看SIL中的
main方法,可以得知w是通過(guò)枚舉值的rawValue的get方法獲取
image
- 查看SIL文件
rawValue的get方法,主要有以下幾步:1、接收一個(gè)枚舉值,用于匹配對(duì)應(yīng)的分支
2、在對(duì)應(yīng)分支創(chuàng)建對(duì)應(yīng)的String
3、返回對(duì)應(yīng)的String

結(jié)論1:使用rawValue的本質(zhì)是調(diào)用get方法
但是get方法中的String是從哪里來(lái)的呢?String存儲(chǔ)在哪里?
- 其實(shí)這些對(duì)應(yīng)分支的字符串在編譯時(shí)期就已經(jīng)存儲(chǔ)好了,即存放在
Maach-O文件的__TEXT.cstring中,且是連續(xù)的內(nèi)存空間,可以通過(guò)編譯后查看Mach-O文件來(lái)驗(yàn)證
image
結(jié)論2:rawValue的get方法中的分支構(gòu)建的字符串,主要是從Mach-O文件對(duì)應(yīng)地址取出的字符串,然后再返回給w
總結(jié)
- 使用
rawValue的本質(zhì)就是在底層調(diào)用get方法,即在get方法中從Mach-O對(duì)應(yīng)地址中取出字符串并返回的操作
區(qū)分 case枚舉值 & rawValue原始值
請(qǐng)問(wèn)下面這段代碼的打印結(jié)果是什么?
//輸出 case枚舉值
print(Week.MON)
//輸出 rawValue
print(Week.MON.rawValue)
<!--打印結(jié)果-->
MON
MON
雖然這兩個(gè)輸出的值從結(jié)果來(lái)看是沒(méi)有什么區(qū)別的,雖然輸出的都是MON,但并不是同一個(gè)東西
第一個(gè)輸出的
case枚舉值第二個(gè)是通過(guò)
rawValue訪問(wèn)的rawValue的get方法
如果我們像下面這種寫法,編譯器就會(huì)報(bào)錯(cuò)

枚舉的init調(diào)用時(shí)機(jī)
主要是探索枚舉的init會(huì)在什么時(shí)候調(diào)用
- 定義一個(gè)符號(hào)斷點(diǎn)
Week.init
image
- 定義如下代碼
print(Week.MON.rawValue)
let w = Week.MON.rawValue
通過(guò)運(yùn)行結(jié)果發(fā)現(xiàn),都是不會(huì)走init方法的
- 如果是通過(guò)
init方式創(chuàng)建enum呢?
print(Week.init(rawValue: "MON"))
運(yùn)行結(jié)果如下

注:這個(gè)斷點(diǎn)首先需要通過(guò)init前的一個(gè)斷點(diǎn) + Week.init符號(hào)斷點(diǎn)+init符號(hào)斷點(diǎn),一起配合,才能斷住
總結(jié):enum中init方法的調(diào)用是通過(guò)枚舉.init(rawValue:)或者枚舉(rawValue:)觸發(fā)的
我們?cè)倮^續(xù)來(lái)分析init方法,來(lái)看下面這段代碼的打印結(jié)果是什么?
print(Week.init(rawValue: "MON"))
print(Week.init(rawValue: "Hello"))
<!--打印結(jié)果-->
Optional(_6_EnumTest.Week.MON)
nil
從結(jié)果中可以看出,第一個(gè)輸出的可選值,第二個(gè)輸出的是nil,表示沒(méi)有找到對(duì)應(yīng)的case枚舉值。為什么會(huì)出現(xiàn)這樣的情況呢?
- 首先分析SIL文件中的
Week.init方法,主要有以下幾步:1、在
init方法中是將所有enum的字符串從Mach-O文件中取出,依次放入數(shù)組中2、放完后,然后調(diào)用
_findStringSwitchCase方法進(jìn)行匹配

其中
-
index_addr 表示獲取當(dāng)前數(shù)組中的第n個(gè)元素值的地址,然后再把構(gòu)建好的字符串放到當(dāng)前地址中
- `struct_extract` 表示`取出當(dāng)前的Int值`,Int類型在系統(tǒng)中也是結(jié)構(gòu)體
- `cond_br` 表示比較的表達(dá)式,即分支條件跳轉(zhuǎn)
- 如果匹配成功,則構(gòu)建一個(gè)`.some的Optional`返回
- 如果匹配不成功,則繼續(xù)匹配,知道最后還是沒(méi)有匹配上,則構(gòu)建一個(gè)`.none的Optional`返回
- 在
swift-source中查找_findStringSwitchCase方法,接收兩個(gè)參數(shù),分別是數(shù)組 + 需要匹配的String- 1、遍歷數(shù)組,如果匹配則返回對(duì)應(yīng)的index
- 2、如果不匹配,則返回-1
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
// 接收一個(gè)數(shù)組 + 需要匹配的string
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
// 遍歷之前創(chuàng)建的字符串?dāng)?shù)組,如果匹配則返回對(duì)應(yīng)的index
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
// 如果不匹配,則返回-1
return -1
}
- 繼續(xù)分析SIL中的
week.init方法- 1、如果沒(méi)有匹配成功,則構(gòu)建一個(gè).none類型的Optional,表示nil
- 2、如果匹配成功,則構(gòu)建一個(gè).some類型的Optional,表示有值

所以,這也是為什么一個(gè)打印可選值,一個(gè)打印nil的原因
枚舉的遍歷
CaseIterable協(xié)議通常用于沒(méi)有關(guān)聯(lián)值的枚舉,用來(lái)訪問(wèn)所有的枚舉值,只需要對(duì)應(yīng)的枚舉遵守該協(xié)議即可,然后通過(guò)allCases獲取所有枚舉值,如下所示
<!--1、定義無(wú)關(guān)聯(lián)值枚舉,并遵守協(xié)議-->
enum Week: String{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
extension Week: CaseIterable{}
<!--2、通過(guò)for循環(huán)遍歷-->
var allCase = Week.allCases
for c in allCase{
print(c)
}
<!--3、通過(guò)函數(shù)式編程遍歷-->
let allCase = Week.allCases.map({"\($0)"}).joined(separator: ", ")
print(allCase)
//******打印結(jié)果******
MON, TUE, WED, THU, FRI, SAT, SUN
關(guān)聯(lián)值
如果希望用枚舉表示復(fù)雜的含義,關(guān)聯(lián)更多的信息,就需要使用關(guān)聯(lián)值了
例如,使用enum表達(dá)一個(gè)形狀,其中有圓形、長(zhǎng)方形等,圓形有半徑,長(zhǎng)方形有寬、高,我們可以通過(guò)下面具有關(guān)聯(lián)值的enum來(lái)表示
//注:當(dāng)使用了關(guān)聯(lián)值后,就沒(méi)有RawValue了,主要是因?yàn)閏ase可以用一組值來(lái)表示,而rawValue是單個(gè)的值
enum Shape{
//case枚舉值后括號(hào)內(nèi)的就是關(guān)聯(lián)值,例如 radius
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
注:具有關(guān)聯(lián)值的枚舉,就
沒(méi)有rawValue屬性了,主要是因?yàn)橐粋€(gè)case可以用一個(gè)或者多個(gè)值來(lái)表示,而rawValue只有單個(gè)的值
這一點(diǎn)我們也可以通過(guò)SIL文件 來(lái)驗(yàn)證
-
首先查看
SIL文件,發(fā)現(xiàn)此時(shí)的enum中既沒(méi)有別名,也沒(méi)有init方法、計(jì)算屬性rawValue了,簡(jiǎn)稱三無(wú)枚舉(個(gè)人叫法,大家隨意哈)
image 其中關(guān)聯(lián)值中radius、width、height這些都是自定義的標(biāo)簽,也可以不寫,如下所示,但并不推薦這種方式,因?yàn)閌可讀性非常差
enum Shape{
//case枚舉值后括號(hào)內(nèi)的就是關(guān)聯(lián)值,例如 radius
case circle(Double)
case rectangle(Int, Int)
}
那么如何創(chuàng)建一個(gè)有關(guān)聯(lián)值的枚舉值呢?可以直接在使用時(shí)給定值來(lái)創(chuàng)建一個(gè)關(guān)聯(lián)的枚舉值
<!--創(chuàng)建-->
var circle = Shape.circle(radius: 10.0)
<!--重新分配-->
circle = Shape.rectangle(width: 10, height: 10)
枚舉的其他用法
模式匹配
enum中的模式匹配其實(shí)就是匹配case枚舉值
簡(jiǎn)單enum的模式匹配
注:swift中的enum模式匹配需要將所有情況都列舉,或者使用default表示默認(rèn)情況,否則會(huì)報(bào)錯(cuò)
enum Week: String{
case MON
case TUE
case WED
case THU
case FRI
case SAT
case SUN
}
var current: Week?
switch current {
case .MON:print(Week.MON.rawValue)
case .TUE:print(Week.MON.rawValue)
case .WED:print(Week.MON.rawValue)
default:print("unknow day")
}
<!--打印結(jié)果-->
unknow day
查看其SIL文件,其內(nèi)部是將nil放入current全局變量,然后匹配case,做對(duì)應(yīng)的代碼跳轉(zhuǎn)

具有關(guān)聯(lián)值enum的模式匹配
關(guān)聯(lián)值的模式匹配主要有兩種:
- 通過(guò)
switch匹配所有case
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let shape = Shape.circle(radius: 10.0)
switch shape{
//相當(dāng)于將10.0賦值給了聲明的radius常量
case let .circle(radius):
print("circle radius: \(radius)")
case let .rectangle(width, height):
print("rectangle width: \(width) height: \(height)")
}
<!--打印結(jié)果-->
circle radius: 10.0
也可以這么寫,將關(guān)聯(lián)值的參數(shù)使用let、var修飾
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let shape = Shape.circle(radius: 10)
switch shape{
//做了Value-Binding,相當(dāng)于將10.0賦值給了聲明的radius常量
case .circle(let radius):
print("circle radius: \(radius)")
case .rectangle(let width, var height):
height += 1
print("rectangle width: \(width) height: \(height)")
}
<!--打印結(jié)果-->
circle radius: 10.0
然后查看SIL中的關(guān)聯(lián)值的模式匹配,如下圖所示
1、首先構(gòu)建一個(gè)關(guān)聯(lián)值的元組
2、根據(jù)當(dāng)前case枚舉值,匹配對(duì)應(yīng)的case,并跳轉(zhuǎn)
-
3、取出元組中的值,將其賦值給匹配case中的參數(shù)
image
- 通過(guò)
if case匹配單個(gè)case,如下所示
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let circle = Shape.circle(radius: 10)
<!--匹配單個(gè)case-->
if case let Shape.circle(radius) = circle {
print("circle radius: \(radius)")
}
- 如果我們只關(guān)心不同case的相同關(guān)聯(lián)值(即關(guān)心不同case的某一個(gè)值),需要使用同一個(gè)參數(shù),例如案例中的x,如果分別使用x、y, 編譯器會(huì)報(bào)錯(cuò)
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.circle(radius: 10)
switch shape{
case let .circle(x), let .square(20, x):
print(x)
default:
break
}
也可以使用通配符_(表示匹配一切)的方式
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(_, x), let .square(_, x):
print("x = \(x)")
default:
break
}
<!--另一種方式-->
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, height: Double)
}
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(x, _), let .square(_, x):
print("x = \(x)")
default:
break
}
注:
- 枚舉使用過(guò)程中不關(guān)心某一個(gè)關(guān)聯(lián)值,可以使用通配符
_表示- OC只能調(diào)用swift中Int類型的枚舉
枚舉的嵌套
枚舉的嵌套主要用于以下場(chǎng)景:
1、【枚舉嵌套枚舉】一個(gè)復(fù)雜枚舉是由一個(gè)或多個(gè)枚舉組成
2、【結(jié)構(gòu)體嵌套枚舉】enum是不對(duì)外公開的,即是私有的
枚舉嵌套枚舉
例如,以吃雞游戲中的方向鍵為例,有上下左右四個(gè)方向鍵,不同的組合會(huì)沿著不同的方向前進(jìn)
enum CombineDirect{
//枚舉中嵌套的枚舉
enum BaseDirect{
case up
case down
case left
case right
}
//通過(guò)內(nèi)部枚舉組合的枚舉值
case leftUp(baseDIrect1: BaseDirect, baseDirect2: BaseDirect)
case leftDown(baseDIrect1: BaseDirect, baseDirect2: BaseDirect)
case rightUp(baseDIrect1: BaseDirect, baseDirect2: BaseDirect)
case rightDown(baseDIrect1: BaseDirect, baseDirect2: BaseDirect)
}
//使用
let leftUp = CombineDirect.leftUp(baseDIrect1: CombineDirect.BaseDirect.left, baseDirect2: CombineDirect.BaseDirect.up)
結(jié)構(gòu)體嵌套枚舉
//結(jié)構(gòu)體嵌套枚舉
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
let key: KeyType
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
枚舉中包含屬性
enum中只能包含計(jì)算屬性、類型屬性,不能包含存儲(chǔ)屬性
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
//編譯器報(bào)錯(cuò):Enums must not contain stored properties 不能包含存儲(chǔ)屬性,因?yàn)閑num本身是值類型
// var radius: Double
//計(jì)算屬性 - 本質(zhì)是方法(get、set方法)
var with: Double{
get{
return 10.0
}
}
//類型屬性 - 是一個(gè)全局變量
static let height = 20.0
}
為什么struct中可以放存儲(chǔ)屬性,而enum不可以?
主要是因?yàn)?code>struct中可以包含存儲(chǔ)屬性是因?yàn)?code>其大小就是存儲(chǔ)屬性的大小。而對(duì)enum來(lái)說(shuō)就是不一樣的(請(qǐng)查閱后文的enum大小講解),enum枚舉的大小是取決于case的個(gè)數(shù)的,如果沒(méi)有超過(guò)255,enum的大小就是1字節(jié)(8位)
枚舉中包含方法
可以在enum中定義實(shí)例方法、static修飾的方法
enum Week: Int{
case MON, TUE, WED, THU, FRI, SAT, SUN
mutating func nextDay(){
if self == .SUN{
self = Week(rawValue: 0)!
}else{
self = Week(rawValue: self.rawValue+1)!
}
}
}
<!--使用-->
var w = Week.MON
w.nextDay()
print(w)
indirect關(guān)鍵字
如果我們想要表達(dá)的enum是一個(gè)復(fù)雜的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)時(shí),可以通過(guò)indirect關(guān)鍵字來(lái)讓當(dāng)前的enum更簡(jiǎn)潔
//用枚舉表示鏈表結(jié)構(gòu)
enum List<T>{
case end
//表示case使是引用來(lái)存儲(chǔ)
indirect case node(T, next: List<T>)
}
<!--也可以將indirect放在enum前-->
//表示整個(gè)enum是用引用來(lái)存儲(chǔ)
indirect enum List<T>{
case end
case node(T, next: List<T>)
}
為什么呢?
- 因?yàn)?code>enum是
值類型,也就意味著他們的大小在編譯時(shí)期就確定了,那么這個(gè)過(guò)程中對(duì)于當(dāng)前的enum的大小是不能確定的,從系統(tǒng)的角度來(lái)說(shuō),不知道需要給enum分配多大的空間,以下是官方文檔的解釋
You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.
- 打印enum的大小
enum List<T>{
case end
indirect case node(T, next: List<T>)
}
print(MemoryLayout<List<Int>>.size)
print(MemoryLayout<List<Int>>.stride)
<!--打印結(jié)果-->
8 //size大小是8
8 //stride大小是8
如果傳入的類型是String呢?

從結(jié)果發(fā)現(xiàn),換成其他類型,其結(jié)果依舊是8,這是為什么呢?
下面來(lái)分析其內(nèi)存結(jié)構(gòu),首先需要定義一個(gè)全局變量
enum List<T>{
case end
indirect case node(T, next: List<T>)
}
var node = List<Int>.node(10, next: List<Int>.end)
print(MemoryLayout.size(ofValue: node))
print(MemoryLayout.stride(ofValue: node))
通過(guò)lldb分析其內(nèi)存

所以indirect關(guān)鍵字其實(shí)就是通知編譯器,我當(dāng)前的enum是遞歸的,大小是不確定的,需要分配一塊堆區(qū)的內(nèi)存空間,用來(lái)存放enum
-
如果是end,此時(shí)存儲(chǔ)的是case值,而case為node時(shí)存儲(chǔ)的是引用地址
image
然后再通過(guò)插件來(lái)查看哪個(gè)地址在堆上,哪個(gè)地址在棧上
image -
這一點(diǎn)也可以通過(guò)SIL來(lái)驗(yàn)證
image -
也可以通過(guò)node的斷點(diǎn)來(lái)驗(yàn)證,確實(shí)是執(zhí)行了
swift_allocObject
image
swift和OC混編enum
在swift中,enum非常強(qiáng)大,可以添加方法、添加extension
而在OC中,enum僅僅只是一個(gè)整數(shù)值
如果想將swift中的enum暴露給OC使用:
- 用
@objc關(guān)鍵字標(biāo)記enum - 當(dāng)前enum應(yīng)該是
Int類型
OC調(diào)用Swift的enum
<!--swift中定義-->
@objc enum Week: Int{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
<!--OC使用-->
- (void)test{
Week mon = WeekMON;
}
Swift調(diào)用OC的enum
OC中的枚舉會(huì)自動(dòng)轉(zhuǎn)換成swift中的enum
<!--OC定義-->
//會(huì)自動(dòng)轉(zhuǎn)換成swift的enum
NS_ENUM(NSInteger, OCENUM){
Value1,
Value2
};
<!--swift使用-->
//1、將OC頭文件導(dǎo)入橋接文件
#import "CJLTest.h"
//2、使用
let ocEnum = OCENUM.Value1
如果OC中是使用typedef enum定義的,自動(dòng)轉(zhuǎn)換成swift就成了下面這樣
typedef enum {
Num1,
Num2
}OCNum;
<!--swift中使用-->
let ocEnum = OCNum.init(0)
print(ocEnum)
//*******打印結(jié)果*******
OCNum(rawValue: 0)
自動(dòng)轉(zhuǎn)換成swift中的如下所示,通過(guò)typedef enum定義的enum,在swift中變成了一個(gè)結(jié)構(gòu)體,并遵循了兩個(gè)協(xié)議:Equatable 和 RawRepresentable

如果在OC中使用typedef NS_ENUM定義枚舉呢?
typedef NS_ENUM(NSInteger, CENUM){
CEnumInvalid = 0,
CEnumA = 1,
CEnumB,
CEnumC
};
自動(dòng)轉(zhuǎn)換成swift后的結(jié)果如下

問(wèn)題:OC如何訪問(wèn)swift中String類型的enum?
- swift中的enum盡量聲明成Int整型
- 然后OC調(diào)用時(shí),使用的是Int整型的
- enum在聲明一個(gè)變量/方法,用于返回固定的字符串,用于在swift中使用
@objc enum Week: Int{
case MON, TUE, WED
var val: String?{
switch self {
case .MON:
return "MON"
case .TUE:
return "TUE"
case .WED:
return "WED"
default:
return nil
}
}
}
<!--OC中使用-->
Week mon = WeekMON;
<!--swift中使用-->
let Week = Week.MON.val
枚舉的大小
主要分析以下幾種情況的大小:
1、普通enum
2、具有關(guān)聯(lián)值的enum
3、enum嵌套enum
4、struct嵌套enum
1、普通enum大小分析
在前面提及enum中不能包含存儲(chǔ)屬性,其根本在于enum的大小與Struct的計(jì)算方式是不一樣的,這里我們將展開詳細(xì)的分析
- 首先,我們先來(lái)看看下面這段代碼的打印結(jié)果是什么?
enum NoMean{
case a
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
0 //size大小是0
1 //表示訪問(wèn)下一個(gè)NoMean的case時(shí),需要跨越1字節(jié)的步長(zhǎng)
- 如果此時(shí)增加一個(gè)
case b,此時(shí)的打印結(jié)果是什么?
enum NoMean{
case a
case b
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
1 //size大小是1
1 //步長(zhǎng)是1
- 如果在增加多個(gè)呢?
enum NoMean{
case a
case b
case c
case d
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)
<!--打印結(jié)果-->
1
1
從結(jié)果來(lái)看,仍然是1,說(shuō)明enum就是以1字節(jié)存儲(chǔ)在內(nèi)存中的,這是為什么呢?我們來(lái)分析下
斷點(diǎn)分析
- 首先通過(guò)斷點(diǎn)來(lái)分析,case分別
a、b、c的情況
image
從斷點(diǎn)可以看出,case是UInt8,即1字節(jié)(8位),最大可以存儲(chǔ)255如果超過(guò)了255,會(huì)自動(dòng)從
UInt8 -> UInt16 -> UInt32 -> UInt64 ...
LLDB分析
- 分別定義4個(gè)全局變量tmp、tmp1、tmp2、tmp3
enum NoMean{
case a
case b
case c
case d
}
var tmp = NoMean.a
var tmp1 = NoMean.b
var tmp2 = NoMean.c
var tmp3 = NoMean.d
通過(guò)lldb查看內(nèi)存情況如下,case都是1字節(jié)大小

普通enum總結(jié)
1、如果
enum中有原始值,即rawValue,其大小取決于case的多少,如果沒(méi)有超過(guò)UInt8即255,則就是1字節(jié)存儲(chǔ)case2、Int標(biāo)識(shí)的其實(shí)就是 RawValue的值
3、當(dāng)只有一個(gè)case的情況下,
size是0,表示這個(gè)enum是沒(méi)有意義的,4、當(dāng)有兩個(gè)及以上case時(shí),此時(shí)的enum是有意義的,如果沒(méi)有超過(guò)255,則
case的步長(zhǎng)是1字節(jié),如果超過(guò),則UInt8->UInt16...,以此類推
2、具有關(guān)聯(lián)值enum的大小分析
如果enum中有關(guān)聯(lián)值,其大小又是多少呢?有如下代碼,打印其size和stride
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
<!--打印結(jié)果-->
17 //size的大小是17
24 //stride的步長(zhǎng)是24
說(shuō)明從打印結(jié)果可以說(shuō)明 enum中有關(guān)聯(lián)值時(shí),其內(nèi)存大小取決于關(guān)聯(lián)值的大小
-
enum有關(guān)聯(lián)值時(shí),關(guān)聯(lián)值的大小 取 對(duì)應(yīng)枚舉關(guān)聯(lián)值 最大的,例如circle中關(guān)聯(lián)值大小是8,而rectangle中關(guān)聯(lián)值大小是16,所以取16。所以enum的size = 最大關(guān)聯(lián)值大小 + case(枚舉值)大小= 16 + 1 = 17,而stride由于8字節(jié)對(duì)齊,所以自動(dòng)補(bǔ)齊到24-
定義一個(gè)全局變量,觀察其內(nèi)存
image
-
總結(jié)
1、
具有關(guān)聯(lián)值的enum大小,取決于最大case的內(nèi)存大小【枚舉大小的本質(zhì)】2、關(guān)聯(lián)值枚舉的大小 = 最大case的內(nèi)存大小 + 1(case的大小)
3、size 表示 實(shí)際大小
4、stride 表示 對(duì)齊后的大?。▋?nèi)存空間中真實(shí)占用的大?。?/p>
3、enum嵌套enum的大小分析
請(qǐng)問(wèn)下面這段代碼的打印結(jié)果是什么?
enum CombineDirect{
enum BaseDirect{
case up, down, left, right
}
case leftUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightUp(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case leftDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
case rightDown(baseDirect1: BaseDirect, baseDirect2: BaseDirect)
}
print(MemoryLayout<CombineDirect>.size)
print(MemoryLayout<CombineDirect>.stride)
<!--打印結(jié)果-->
2 //size大小,enum有關(guān)聯(lián)值取決于關(guān)聯(lián)值的大小,每個(gè)case都有2個(gè)大小為1的enum,所以為2
2 //stride大小
從結(jié)果中說(shuō)明enum嵌套enum同具有關(guān)聯(lián)值的enum是一樣的,同樣取決于關(guān)聯(lián)值的大小,其內(nèi)存大小是最大關(guān)聯(lián)值的大小
通過(guò)嵌套枚舉定義一個(gè)全局變量
var combine = CombineDirect.leftDown(baseDirect1: .left, baseDirect2: .down)
查看其內(nèi)存情況如下

這里我們會(huì)有一個(gè)疑問(wèn),其中的
81到底指的是什么?這里先提前劇透下:8表示 case leftDown的枚舉值,1表示其中down的枚舉值,下面我們來(lái)驗(yàn)證
在上面這個(gè)例子中,是有4個(gè)case,其case在內(nèi)存中是用0、4、8、12體現(xiàn)的,如果是有很多個(gè)case,是否還滿足我們現(xiàn)在這樣的規(guī)律呢?
-
【嘗試1】:在4個(gè)case的基礎(chǔ)上增加了10個(gè)case
- 查看
case downDown1,在內(nèi)存中為0x1,即1
嘗試1-1 - 查看
case rightUp,在內(nèi)存中為0xb,即11
嘗試1-2
從這里可以發(fā)現(xiàn)case是從0、1、2....這樣依次往后的順序
- 查看
-
【嘗試2】:如果去掉其中的幾種情況呢,發(fā)現(xiàn)case依舊是0、1、2....
嘗試2 -
【嘗試3】:當(dāng)只有2個(gè)case時(shí),發(fā)現(xiàn)case的枚舉值是
0、8
嘗試3 -
【嘗試4】:當(dāng)有3個(gè)case時(shí),發(fā)現(xiàn)case的枚舉值是
0、4、8
嘗試4
PS:至于為什么會(huì)是這樣的結(jié)果,目前也沒(méi)找到任何依據(jù),后續(xù)如果有了依據(jù),再來(lái)補(bǔ)充吧(有知道的童鞋,歡迎留言~)
總結(jié)
enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小當(dāng)嵌套enum的case只有
2個(gè)時(shí),case在內(nèi)存中的存儲(chǔ)是0、8當(dāng)嵌套enum的case大于2,小于等于4時(shí),case在內(nèi)存中的存儲(chǔ)是
0、4、8、12當(dāng)嵌套enum的case
大于4時(shí),case在內(nèi)存中的存儲(chǔ)是從0、1、2...以此類推
4、結(jié)構(gòu)體嵌套enum的大小分析
請(qǐng)問(wèn)下面這段代碼的打印結(jié)果是什么?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
let key: KeyType
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
1
1
- 如果只嵌套了enum,沒(méi)有聲明變量,結(jié)構(gòu)體的大小是多少呢?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
0 //size的大小取決于成員變量,但是struct中目前沒(méi)有屬性
1
- 如果不僅有枚舉變量,還有其他屬性,結(jié)構(gòu)體的大小是多少呢?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
let key: KeyType //1字節(jié)
var height: UInt8 //1字節(jié)
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
2
2
- 如果在增加一個(gè)Int類型的屬性呢?
struct Skill {
enum KeyType{
case up
case down
case left
case right
}
var width: Int //8字節(jié)
let key: KeyType //1字節(jié)
var height: UInt8 //1字節(jié)
func launchSkill(){
switch key {
case .left, .right:
print("left, right")
case .up, .down:
print("up, down")
}
}
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
10 //size大?。ㄅcOC中的結(jié)構(gòu)體大小計(jì)算是一致的,min(m,n),其中m表示存儲(chǔ)的位置,n表示屬性的大小,要求是:m必須整除n)
16 //stride大小
結(jié)論
1、如果結(jié)構(gòu)體中沒(méi)有其他屬性,只有枚舉變量,那么結(jié)構(gòu)體的大小就是枚舉的大小,即size為1
2、如果結(jié)構(gòu)體中嵌套了enum,但是沒(méi)有聲明變量,此時(shí)的size是0,stride是1
3、如果結(jié)構(gòu)體中還有其他屬性,則按照OC中的
結(jié)構(gòu)體內(nèi)存對(duì)齊三原則進(jìn)行分析(參考iOS-底層原理 05:內(nèi)存對(duì)齊原理這篇文章)
內(nèi)存對(duì)齊 & 字節(jié)對(duì)齊 區(qū)分
內(nèi)存對(duì)齊:iOS中是8字節(jié)對(duì)齊,蘋果實(shí)際分配采用16字節(jié)對(duì)齊,這種只會(huì)在分配對(duì)象時(shí)出現(xiàn)字節(jié)對(duì)齊:存儲(chǔ)屬性的位置必須是偶地址,即OC內(nèi)存對(duì)齊中的min(m,n),其中m表示存儲(chǔ)的位置,n表示屬性的大小,需要滿足位置m整除n時(shí),才能從該位置存放屬性。簡(jiǎn)單來(lái)說(shuō),就是必須在自身的倍數(shù)位置開始外部調(diào)用對(duì)象時(shí),對(duì)象是服從內(nèi)存對(duì)齊。單純從
結(jié)構(gòu)上說(shuō),結(jié)構(gòu)內(nèi)部服從最大字節(jié)對(duì)齊。
例如下面這個(gè)例子
struct Skill {
var age: Int //8字節(jié)
var height: UInt8 //1字節(jié)
var width: UInt16 //2字節(jié)
}
print(MemoryLayout<Skill>.size)
print(MemoryLayout<Skill>.stride)
<!--打印結(jié)果-->
12
16
size為12的原因是:內(nèi)存從0位置開始Int是占據(jù)0-7,UInt8占據(jù)8,下一個(gè)位置是9,但是UInt16是2字節(jié)對(duì)齊的要在它的倍數(shù)位置開始所以找下一個(gè)可以整除它的位置也就是UInt16占據(jù)10-11正好整個(gè)size在0-11,所以size為12stride為16的原因:stride是實(shí)際分配的,必須是最大屬性大小的整數(shù)倍,即8的倍數(shù),所以是16
總結(jié)
- 枚舉說(shuō)明:
1、enum中使用
rawValue的本質(zhì)是調(diào)用get方法,即在get方法中從Mach-O對(duì)應(yīng)地址中取出字符串并返回的操作2、enum中
init方法的調(diào)用是通過(guò)枚舉.init(rawValue:)或者枚舉(rawValue:)觸發(fā)的3、沒(méi)有
關(guān)聯(lián)值的enum,如果希望獲取所有枚舉值,需要遵循CaseIterable協(xié)議,然后通過(guò)枚舉名.allCase的方式獲取4、case枚舉值和rawValue原始值的關(guān)系:
case 枚舉值 = rawValue原始值5、具有關(guān)聯(lián)值的枚舉,可以成為
三無(wú)enum,因?yàn)闆](méi)有別名RawValue、init、計(jì)算屬性rawValue6、enum的模式匹配方式,主要有兩種:
switch / if case7、enum可以嵌套enum,也可以在結(jié)構(gòu)體中嵌套enum,表示該enum是struct私有的
8、enum中還可以
包含計(jì)算屬性、類型屬性,但是不能包含存儲(chǔ)屬性9、enum中可以定義
實(shí)例 + static修飾的方法
- 枚舉內(nèi)存大小結(jié)論:
1、普通enum的內(nèi)存大小一般是
1字節(jié),如果只有一個(gè)case,則為0,表示沒(méi)有意義,如果case個(gè)數(shù)超過(guò)255,則枚舉值的類型由UInt8->UInt16->UInt32...2、
具有關(guān)聯(lián)值的enum大小,取決于最大case的內(nèi)存大小+case的大?。?字節(jié))3、
enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小4、結(jié)構(gòu)體嵌套enum,如果
沒(méi)有屬性,則size為0,如果只有enum屬性,size為1,如果還有其他屬性,則按照OC中內(nèi)存對(duì)齊原則進(jìn)行計(jì)算


















