前言
本篇文章將講述Swift中很常用的也很重要的一個(gè)知識(shí)點(diǎn) ?? Enum枚舉
。首先會(huì)介紹與OC中枚舉的差別,接著會(huì)從底層分析Enum
的使用場(chǎng)景,包含枚舉的嵌套
和遞歸
汉形,與OC的混編
的場(chǎng)景,最后分析枚舉的內(nèi)存大小
的計(jì)算方式庵楷,希望大家能夠掌握童漩。
一期奔、OC&Swift枚舉的區(qū)別
1.1 OC中的NS_ENUM
OC中的枚舉和C/C++中的枚舉基本一樣,具有以下特點(diǎn)??
- 僅支持
Int類型
罗晕,默認(rèn)首元素值為0
酬屉,后續(xù)元素值依次+1
-
中間的元素
有賦值,那么以此賦值為準(zhǔn)
揍愁,后續(xù)沒(méi)賦值
的元素值依舊依次+1
枚舉使用示例代碼??
typedef NS_ENUM(NSInteger, WEEK) {
Mon,
Tue = 10,
Wed,
Thu,
Fri,
Sat,
Sun
};
// 調(diào)用代碼??
WEEK a = Mon;
WEEK b = Tue;
NSLog(@"a = %d, b = %d", (int)a, (int)b);
1.2 Swift中的Enum
Swift中的枚舉比OC的強(qiáng)大很多
呐萨!其特點(diǎn)如下??
- 格式: 不用逗號(hào)分隔,類型需使用case聲明
- 內(nèi)容:
- 支持
Int吗垮、Double烁登、String
等基礎(chǔ)類型
礼患,也有默認(rèn)枚舉值
(String類型
默認(rèn)枚舉值為key的名稱
厨相,Int、Double
數(shù)值型默認(rèn)枚舉值為0
開(kāi)始+1遞增
- 支持
自定義選項(xiàng)
??不指定
支持類型
褥傍,就沒(méi)有rawValue
锦募,但同樣支持case枚舉
,可自定義關(guān)聯(lián)內(nèi)容
- 支持
注意:
rawValue
在后面枚舉的訪問(wèn)
中會(huì)詳細(xì)的講解狭归。
示例代碼??
// 寫(xiě)法一
// 不需要逗號(hào)隔開(kāi)
enum Weak1 {
case MON
case TUE
case WED
case THU
case FRI
case SAT
case SUN
}
// 寫(xiě)法二
// 也可以直接一個(gè)case,然后使用逗號(hào)隔開(kāi)
enum Weak2 {
case MON, TUE, WED, THU, FRI, SAT, SUN
}
// 定義一個(gè)枚舉變量
var w: Weak1 = .MON
1.2.2 自定義選項(xiàng)類型
如果在聲明枚舉時(shí)不指定類型
股耽,那么可給枚舉項(xiàng)添加拓展內(nèi)容
(即自定義類型
)震嫉。switch-case
訪問(wèn)時(shí)瞳浦,可取出拓展類型進(jìn)行相應(yīng)的操作朋魔。例如??
// 自定義類型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
func printValue(_ v: Shape) {
// switch區(qū)分case(不想每個(gè)case處理镶奉,可使用default)
switch v {
case .square(let width):
print(width)
case .circle(let radius, _):
print(radius)
}
}
let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
printValue(s)
printValue(c)
二、Swift枚舉的使用
接下來(lái)我們看看Swift枚舉的使用,包含一些特殊的場(chǎng)景的情況物延。
2.1 枚舉的訪問(wèn)
說(shuō)到枚舉的訪問(wèn),就必須得提一個(gè)關(guān)鍵字rawValue
惨好,使用案例??
enum Weak: String{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
var w = Weak.MON.rawValue
print(w)
運(yùn)行結(jié)果??
注意:如果enum
沒(méi)有聲明類型
缚甩,是沒(méi)有rawValue
屬性的??
現(xiàn)在問(wèn)題來(lái)了 ?? rawValue
對(duì)應(yīng)在底層是如何做到讀取到MON
值的?
rawValue的取值流程
老規(guī)矩,找入口茸歧,之前我們都是查看SIL,當(dāng)然這里也不例外??
swiftc -emit-sil xx.swift | xcrun swift-demangle >> ./xx.sil && vscode xx.sil
先看看枚舉Week??
接著看看main函數(shù)的流程??
最后看看rawValue的getter方法??
然后看bb8代碼段??
至此件余,我們現(xiàn)在知道了,rawValue
的底層就是調(diào)用的getter方法
乙漓,getter方法中構(gòu)造了字符串
,但是這個(gè)字符串的值
(例如“MON”)從哪里取出
的呢愈魏?其實(shí)我們能猜出來(lái)虫碉,應(yīng)該是在編譯期確定
了的第喳,所以,我們打開(kāi)工程的exec可執(zhí)行文件弟胀,查看Mach-O
??
可見(jiàn),在__TEXT, __cstring
的section段,這些字符串在編譯期已經(jīng)存儲(chǔ)好了,而且內(nèi)存地址是連續(xù)的
。所以垃它,rawValue
的getter方法 ?? case分支中構(gòu)建的字符串,主要是在Mach-O
文件中從對(duì)應(yīng)地址
取出的字符串捂贿,然后再返回給變量w
蕴坪。
case值 & rawValue值
現(xiàn)在我們弄清楚了rawValue
值的來(lái)源冤灾,那么又有一個(gè)問(wèn)題:枚舉case
值和 rawValue
值如何區(qū)分
呢?下面的代碼輸出打印結(jié)果是什么?
//輸出 case值
print(Weak.MON)
//輸出 rawValue值
print(Weak.MON.rawValue)
雖然輸出的都是MON
疯趟,但其實(shí)并不是相同的拘哨,why?看下圖??
上圖可知信峻,并不能將枚舉的case值
賦給字符串類型常量w
倦青,同時(shí),也不能將字符串"MON"
賦給枚舉值t
盹舞。
2.2 枚舉初始化init
在OC中产镐,枚舉沒(méi)有初始化一說(shuō),而在Swift中踢步,枚舉是有init初始化方法
??
Weak.init(rawValue:)
接下來(lái)我們來(lái)看看這個(gè)初始化方法在底層的流程
磷账,首先添加代碼??,打上斷點(diǎn)
print(Weak.init(rawValue: "MON")!)
打開(kāi)匯編贾虽,運(yùn)行??
接著我們還是看SIL代碼逃糟,關(guān)于Weak.init(rawValue:)
部分??
其中,上圖中涉及的SIL的指令釋義??
指令名稱 | 指令釋義 |
---|---|
index_addr | 獲取當(dāng)前數(shù)組中的第n個(gè)元素值 的地址(即指針 )蓬豁,存儲(chǔ)到當(dāng)前地址中 |
struct_extract | 表示在結(jié)構(gòu)體中取出當(dāng)前的Int值 绰咽,Int類型在系統(tǒng)中也是結(jié)構(gòu)體 |
cond_br | 表示比較的表達(dá)式 ,即分支條件跳轉(zhuǎn) (類似于三元表達(dá)式) |
接著來(lái)看看這個(gè)關(guān)鍵的函數(shù)_findStringSwitchCase
的源碼??
我們繼續(xù)看Weak.init
的最終處理代碼 ?? bb29
代碼段??
至此地粪,我們分析完了Weak.init
的底層流程取募,于是修改之前的調(diào)用代碼(去掉
了之前的感嘆號(hào)!
)??
print(Weak.init(rawValue: "MON"))
print(Weak.init(rawValue: "Hello"))
編譯器會(huì)爆出警告(返回的結(jié)果是可選型
),運(yùn)行結(jié)果??
所以蟆技,現(xiàn)在我們就能明白玩敏,為什么一個(gè)打印的是可選值,一個(gè)打印的是nil质礼。
2.3 枚舉遍歷:CaseIterable協(xié)議
CaseIterable協(xié)議
旺聚,有allCases
屬性,支持遍歷所有
case眶蕉,例如??
// Double類型
enum Week1: Double, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week1.allCases.forEach { print($0.rawValue)}
// String類型
enum Week2: String, CaseIterable {
case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week2.allCases.forEach { print($0.rawValue)}
2.4 枚舉關(guān)聯(lián)值
關(guān)聯(lián)值
就是上面講過(guò)的自定義類型
的枚舉砰粹,它能表示更復(fù)雜的信息,與普通類型的枚舉不同點(diǎn)在于??
- 沒(méi)有rawValue
- 沒(méi)有rawValue的getter方法
- 沒(méi)有初始化init方法
例如
// 自定義類型的使用
enum Shape {
case square(width: Double)
case circle(radius: Double, borderWidth:Double)
}
查看其SIL代碼??
中間層代碼真的什么都沒(méi)有造挽!??
2.5 模式匹配
模式匹配
就是針對(duì)case的匹配
碱璃,根據(jù)枚舉類型弄痹,分為2種:
- 簡(jiǎn)單類型的枚舉的模式匹配
- 自定義類型的枚舉(關(guān)聯(lián)值)的模式匹配
2.5.1 簡(jiǎn)單類型
swift中的簡(jiǎn)單類型enum匹配需要將
所有情況都列舉
,或者使用default表示默認(rèn)情況
嵌器,否則會(huì)報(bào)錯(cuò)
肛真!
enum Weak: String{
case MON
case TUE
case WED
case THU
case FRI
case SAT
case SUN
}
var current: Weak?
switch current {
case .MON:print(Weak.MON.rawValue)
case .TUE:print(Weak.MON.rawValue)
case .WED:print(Weak.MON.rawValue)
default:print("unknow day")
}
如果去掉default
,會(huì)報(bào)錯(cuò)??
我們看看SIL代碼??
所以運(yùn)行上面代碼爽航,應(yīng)該匹配的是default分支??
2.5.2 關(guān)聯(lián)值類型
關(guān)聯(lián)值類型的模式匹配有兩種方式??
- switch - case ?? 匹配所有case
- if - case ?? 匹配單個(gè)case
switch - case
enum Shape{
case circle(radius: Double)
case rectangle(width: Int, height: Int)
}
let
修飾case
值??
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)")
}
也可以let var
修飾關(guān)聯(lián)值的入?yún)?/code>??
let shape = Shape.circle(radius: 10.0)
switch shape{
case .circle(let radius):
print("circle radius: \(radius)")
case .rectangle(let width, var height):
height += 1
print("rectangle width: \(width) height: \(height)")
}
查看SIL層的代碼蚓让,看看是怎么匹配的??
if - case
let circle = Shape.circle(radius: 10.0)
if case let Shape.circle(radius) = circle {
print("circle radius: \(radius)")
}
通用關(guān)聯(lián)值
如果只關(guān)心不同case
下的某一個(gè)關(guān)聯(lián)值
,可以將該關(guān)聯(lián)值用同一個(gè)入?yún)?/code>替換岳掐,例如下面例子中的
x
??
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:
print("未匹配")
break
}
注意:不能使用多于1個(gè)的通用入?yún)⑵敬缦旅娴?code>y??
也可以使用通配符 _
??
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(_, x), let .square(_, x):
print("x = \(x)")
default:
break
}
還可以這么寫(xiě)??
let shape = Shape.rectangle(width: 10, height:20)
switch shape{
case let .rectangle(x, _), let .square(_, x):
print("x = \(x)")
default:
break
}
大家平時(shí)在使用枚舉時(shí)饭耳,還是要注意下面2點(diǎn)??
- 枚舉使用過(guò)程中不關(guān)心某一個(gè)關(guān)聯(lián)值串述,可以使用
通配符_
標(biāo)識(shí)- OC只能調(diào)用Swift中
Int類型
的枚舉
2.6 支持計(jì)算型屬性 & 函數(shù)
Swift枚舉中還支持計(jì)算屬性
和函數(shù)
,例如??
enum Direct: Int {
case up
case down
case left
case right
// 計(jì)算型屬性
var description: String{
switch self {
case .up:
return "這是上面"
default:
return "這是\(self)"
}
}
// 函數(shù)
func printSelf() {
print(description)
}
}
Direct.down.printSelf()
三寞肖、枚舉嵌套
枚舉的嵌套主要有2種場(chǎng)景??
- 枚舉嵌套枚舉
- 結(jié)構(gòu)體嵌套枚舉
3.1 enum嵌套enum
我們先來(lái)看看枚舉嵌套枚舉
纲酗,我們繼續(xù)改下上面的例子??
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)
}
如果初始化一個(gè)左上方向,代碼??
let leftUp = CombineDirect.leftUp(baseDIrect1: CombineDirect.BaseDirect.left, baseDirect2: CombineDirect.BaseDirect.up)
3.2 struct嵌套enum
接下來(lái)就是結(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")
}
}
}
3.3 枚舉的遞歸(indirect)
還有一種特殊的場(chǎng)景 遞歸
?? 枚舉中case關(guān)聯(lián)內(nèi)容
使用自己的枚舉類型
觅赊。例如??
enum Binary<T> {
case empty
case node(left: Binary, value:T, right:Binary)
}
一個(gè)樹(shù)
結(jié)構(gòu),其左右節(jié)點(diǎn)的類型也是自己本身琼稻,這時(shí)編譯器會(huì)報(bào)錯(cuò)??
報(bào)錯(cuò)原因 ?? 使用該枚舉時(shí)吮螺,enum的大小
需要case
來(lái)確定,而case的大小又需要使用到enum大小
帕翻。所以無(wú)法計(jì)算
enmu的大小鸠补,于是報(bào)錯(cuò)!
安排 ?? 根據(jù)編譯器提示嘀掸,需要使用關(guān)鍵字indirect
紫岩,意思就是將該枚舉標(biāo)記位遞歸
,同時(shí)也支持標(biāo)記單個(gè)case
睬塌,所以可以??
那么問(wèn)題來(lái)了泉蝌,indirect
在底層干了什么呢?
indirect底層原理
我們先來(lái)看這個(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))
size和stride都是8揩晴,換成String類型??
仍然也是8勋陪,看來(lái)枚舉的大小不受其模板類型大小的影響。
lldb分析
我們先lldb看看其內(nèi)存的分布??
上圖中我們發(fā)現(xiàn)硫兰,node的metadata
對(duì)應(yīng)的地址0x0000000100562660
是分配在堆
上的粥鞋,所以,indirect關(guān)鍵字其實(shí)就是通知編譯器
瞄崇,需要分配一塊堆區(qū)
的內(nèi)存空間呻粹,用來(lái)存放enum
或 case
壕曼。此時(shí)case為node
時(shí),存儲(chǔ)的是引用地址0x0000000100562660
等浊,而case為end
時(shí)腮郊,則??
那為何說(shuō)地址是堆區(qū)呢?我們接著看看SIL代碼??
SIL代碼中筹燕,是通過(guò)alloc_box申請(qǐng)的內(nèi)存轧飞,alloc_box底層調(diào)用的是swift_allocObject
,所以是堆區(qū)撒踪,我們可以再node打上斷點(diǎn)过咬,查看匯編??
四、##swift和OC混編枚舉
接下來(lái)我們看看swift和OC的枚舉的混編場(chǎng)景制妄。
4.1 OC使用Swift枚舉
首先看看OC調(diào)用Swift枚舉掸绞,那么此時(shí)枚舉必須具備以下2個(gè)條件??
- 用
@objc
關(guān)鍵字標(biāo)記enum - 當(dāng)前enum必須是
Int類型
// Swift中定義枚舉
@objc enum Weak: Int{
case MON, TUE, WED, THU, FRI, SAT, SUN
}
// OC使用
- (void)test{
Weak mon = WeakMON;
}
4.2 Swift使用OC枚舉
反過(guò)來(lái),就沒(méi)限制了耕捞,OC中的枚舉會(huì)自動(dòng)轉(zhuǎn)換
成swift中的enum衔掸。
// OC定義
NS_ENUM(NSInteger, OCENUM){
Value1,
Value2
};
// swift使用
//1、將OC頭文件導(dǎo)入橋接文件
#import "OCFile.h"
//2俺抽、使用
let ocEnum = OCENUM.Value1
typedef enum
// OC定義
typedef enum {
Num1,
Num2
}OCNum;
// swift使用
let ocEnum = OCNum.init(0)
print(ocEnum)
上圖可知敞映,通過(guò)typedef enum
定義的enum,在swift中變成了一個(gè)結(jié)構(gòu)體磷斧,并遵循了兩個(gè)協(xié)議:Equatable
和 RawRepresentable
振愿。
typedef NS_ENUM
// OC定義
typedef NS_ENUM(NSInteger, OCNum) {
Num1,
Num2
};
// swift使用
let ocEnum = OCNum.init(rawValue: 0)
print(ocEnum!)
那么自動(dòng)生成的swift中是這樣??
并沒(méi)有遵循任何協(xié)議!
4.3 OC使用Swift中String類型的枚舉
這也是一種常見(jiàn)的場(chǎng)景弛饭,解決方案??
- swift中的enum盡量聲明成Int整型
- 然后OC調(diào)用時(shí)冕末,使用的是Int整型的
- enum再聲明一個(gè)
變量/方法
,用于返回固定的字符串
孩哑,給swift中使用
示例??
@objc enum Weak: 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中使用
Weak mon = WeakMON;
// swift中使用
let weak = Weak.MON.val
五栓霜、枚舉的大小
主要分析以下幾種情況??
- 普通enum
- 具有關(guān)聯(lián)值的enum
- enum嵌套enum
- struct嵌套enum
枚舉的大小也是面試中經(jīng)常問(wèn)到的問(wèn)題,重點(diǎn)在于兩個(gè)函數(shù)的區(qū)別??
size
:實(shí)際占用
內(nèi)存大小
stride
:系統(tǒng)分配
的內(nèi)存大小
5.1 普通enum
最普通的情況横蜒,即非嵌套胳蛮,非自定義類型
的枚舉,例如??
enum Weak {
case MON
}
print(MemoryLayout<Weak>.size)
print(MemoryLayout<Weak>.stride)
再添加一個(gè)case丛晌,運(yùn)行??
繼續(xù)增加多個(gè)case仅炊,運(yùn)行??
以上可以看出,當(dāng)case個(gè)數(shù)為1時(shí)澎蛛,枚舉size為0抚垄,個(gè)數(shù)>=2時(shí),size的大小始終是1,why呆馁?下面我們來(lái)分析分析??
上圖打斷點(diǎn)桐经,讀取內(nèi)存可以看出,case都是1字節(jié)
大小浙滤,1個(gè)字節(jié)是8個(gè)byte
阴挣,按照二進(jìn)制轉(zhuǎn)換成十進(jìn)制,那么有255種排列組合(0x00000000 - 0x11111111)
纺腊,所以當(dāng)case為1個(gè)的時(shí)候畔咧,size的大小是0
(二進(jìn)制是0x0
),case數(shù)<=255
時(shí)揖膜,size都是1
誓沸。而超過(guò)255
個(gè)時(shí),會(huì)自動(dòng)擴(kuò)容
壹粟,size
和stride
都會(huì)增加
拜隧。
5.2 具有關(guān)聯(lián)值的enum
如果是自定義類型的枚舉,即關(guān)聯(lián)值類型煮寡,size和stride的值會(huì)發(fā)生什么變化呢虹蓄?看下面的例子??
enum Shape{
case circle(radius: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
看來(lái)關(guān)聯(lián)值的枚舉大小和關(guān)聯(lián)值入?yún)⒂嘘P(guān)系犀呼,??
5.3 enum嵌套enum
枚舉嵌套枚舉幸撕,是一種特殊的情況,下面示例大小是多少外臂???
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é)果中可以看出坐儿,enum嵌套enum,和具有關(guān)聯(lián)值的enum
的情況是一樣的宋光,同樣取決于關(guān)聯(lián)值的大小
貌矿,其內(nèi)存大小是最大關(guān)聯(lián)值的大小
。
接著我們看看具體的分布罪佳,可以先定義一個(gè)變量??
var combine = CombineDirect.leftDown(baseDirect1: .left, baseDirect2: .down)
lldb查看其內(nèi)存分布??
將第一個(gè)入?yún)eft 改為 up??
所以逛漫,02
表示是caseleftDown
的關(guān)聯(lián)值的第一個(gè)入?yún)?/code>的枚舉值,那第2個(gè)入?yún)⑹?down赘艳,按照規(guī)律來(lái)算應(yīng)該是
01
酌毡,但卻是81
,why蕾管?接下來(lái)我們看看81
代表的是什么值?
- 在enum CombineDirect中
多加4個(gè)case
結(jié)果是c1
- 減少一個(gè)case
減少一個(gè)case項(xiàng)后,是a1
- 再減少一個(gè)case
再減少一個(gè)是81涉馅,說(shuō)明81
中的8
是
- 繼續(xù)減少倘是,保證case只有2個(gè)
結(jié)果頁(yè)是81
- 添加保證case>10個(gè)
如果leftDown 的case索引值大于8,例如上圖,leftDown是第1個(gè)case掏熬,結(jié)果e1
中的e
就是15(十進(jìn)制)佑稠,即case選項(xiàng)的索引值。
- 再減少旗芬,保證case leftDown在第9個(gè)
果然讶坯,上圖中l(wèi)eftDown的case索引值是9,打印出來(lái)的91
中的第一位也是9
岗屏。
綜上辆琅, 81
中的第一位8
這個(gè)值,有以下幾種情況區(qū)分??
- 當(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...
類推
81
中的1
而涉,代表什么意思呢?我們改變下關(guān)聯(lián)值入?yún)??
所以联予,leftDown減少一個(gè)入?yún)⑻湎兀Y(jié)果是80
,加一個(gè)入?yún)⒎芯茫Y(jié)果是01 80
季眷,繼續(xù)再加一個(gè)入?yún)??
關(guān)聯(lián)值的入?yún)⑹?code>up,down卷胯,right子刮,right,對(duì)應(yīng)的枚舉值是0窑睁,1挺峡,3,3
卵慰,所以可以得出結(jié)論??
- enum嵌套enum同樣取決于
最大case的關(guān)聯(lián)值大小
- case中關(guān)聯(lián)值的
內(nèi)存分布
沙郭,又是根據(jù)入?yún)⒌?code>個(gè)數(shù)和大小
來(lái)分布的
2.1 每個(gè)入?yún)⒄?code>一個(gè)字節(jié)大小的空間(即2個(gè)byte位
),第2位byte里面存儲(chǔ)的是內(nèi)層枚舉的case值
裳朋,第1位的byte值通常是0
2.2最后一個(gè)入?yún)?/code>的byte空間分布 ?? 第2位是
內(nèi)層
枚舉的case值病线,第1位是外層
枚舉的case值吓著,其規(guī)律又如下??
- 當(dāng)外層enum的case
只有2個(gè)
時(shí),第1位byte值按照0送挑、8
依次分布- 當(dāng)外層enum的case個(gè)數(shù)
>2绑莺,<=4
時(shí),第1位byte值按照0惕耕、4纺裁、8、12
依次分布- 當(dāng)外層enum的case個(gè)數(shù)
>4
時(shí)司澎,第1位byte值按照0欺缘、1、2挤安、3谚殊、...
依次分布
5.4 struct嵌套enum
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)
size和stride都是1。結(jié)構(gòu)體的大小計(jì)算蛤铜,跟函數(shù)無(wú)關(guān)嫩絮,所以只看成員變量key的大小
,key是枚舉Skill
類型围肥,大小為1剿干,所以結(jié)構(gòu)體大小為1。繼續(xù)穆刻,去掉
成員key??
沒(méi)有任何成員變量時(shí)置尔,size為0,stride為1(系統(tǒng)默認(rèn)分配
的)蛹批。如果加一個(gè)成員??
因?yàn)樘砑拥氖?code>UInt8撰洗,占1個(gè)字節(jié)篮愉,所以size和stride都+1腐芍,均為2。再添加一個(gè)成員??
添加的成員是Int類型试躏,占8字節(jié)猪勇,8+1+1=10,而stride是系統(tǒng)分配的颠蕴,8的倍數(shù)來(lái)分配泣刹,所以是16。你以為就這么簡(jiǎn)單的相加嗎犀被?我們換一下width的位置
??
將width成員放到最后面椅您,size變?yōu)?6,why寡键?因?yàn)閟ize的大小掀泳,是按照結(jié)構(gòu)體內(nèi)存對(duì)齊原則
來(lái)計(jì)算的,可參考我之前的文章內(nèi)存對(duì)齊分析。
總結(jié)
本篇文章主要講解了Swift中的枚舉员舵,開(kāi)始與OC的枚舉作比較脑沿,引出Swift枚舉的不同點(diǎn),進(jìn)而分析了rawValue
和初始化init
的底層實(shí)現(xiàn)流程马僻,然后講解了幾個(gè)重要的場(chǎng)景 ?? OC和Swift的橋接
場(chǎng)景庄拇,枚舉嵌套
的場(chǎng)景,最后重點(diǎn)分析了枚舉的大小
韭邓,即內(nèi)存分布的情況措近,這也是面試中經(jīng)常出的題目,希望大家掌握女淑,謝謝熄诡!