Swift 枚舉

前言

本篇文章將講述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)容:
    1. 支持Int吗垮、Double烁登、String基礎(chǔ)類型礼患,也有默認(rèn)枚舉值String類型默認(rèn)枚舉值為key的名稱厨相,Int、Double數(shù)值型默認(rèn)枚舉值為0開(kāi)始+1遞增
    2. 支持自定義選項(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)在于??

  1. 沒(méi)有rawValue
  2. 沒(méi)有rawValue的getter方法
  3. 沒(méi)有初始化init方法

例如

// 自定義類型的使用
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

查看其SIL代碼??

中間層代碼真的什么都沒(méi)有造挽!??

2.5 模式匹配

模式匹配就是針對(duì)case的匹配碱璃,根據(jù)枚舉類型弄痹,分為2種:

  1. 簡(jiǎn)單類型的枚舉的模式匹配
  2. 自定義類型的枚舉(關(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)??

  1. 枚舉使用過(guò)程中不關(guān)心某一個(gè)關(guān)聯(lián)值串述,可以使用通配符_標(biāo)識(shí)
  2. 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)景??

  1. 枚舉嵌套枚舉
  2. 結(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)存放enumcase壕曼。此時(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è)條件??

  1. @objc關(guān)鍵字標(biāo)記enum
  2. 當(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é)議:EquatableRawRepresentable振愿。

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)景弛饭,解決方案??

  1. swift中的enum盡量聲明成Int整型
  2. 然后OC調(diào)用時(shí)冕末,使用的是Int整型的
  3. 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

五栓霜、枚舉的大小

主要分析以下幾種情況??

  1. 普通enum
  2. 具有關(guān)聯(lián)值的enum
  3. enum嵌套enum
  4. 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ò)容壹粟,sizestride都會(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ū)分??

  1. 當(dāng)嵌套enum的case只有2個(gè)時(shí)这刷,case在內(nèi)存中的存儲(chǔ)是0婉烟、8
  2. 當(dāng)嵌套enum的case大于2,小于等于4時(shí)暇屋,case在內(nèi)存中的存儲(chǔ)是0似袁、4、8咐刨、12
  3. 當(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é)論??

  1. enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小
  2. 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)常出的題目,希望大家掌握女淑,謝謝熄诡!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诗力,隨后出現(xiàn)的幾起案子凰浮,更是在濱河造成了極大的恐慌,老刑警劉巖苇本,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袜茧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瓣窄,警方通過(guò)查閱死者的電腦和手機(jī)笛厦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俺夕,“玉大人裳凸,你說(shuō)我怎么就攤上這事∪懊常” “怎么了姨谷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)映九。 經(jīng)常有香客問(wèn)我梦湘,道長(zhǎng),這世上最難降的妖魔是什么件甥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任捌议,我火速辦了婚禮,結(jié)果婚禮上引有,老公的妹妹穿的比我還像新娘瓣颅。我一直安慰自己,他們只是感情好譬正,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布宫补。 她就那樣靜靜地躺著僻孝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪守谓。 梳的紋絲不亂的頭發(fā)上穿铆,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音斋荞,去河邊找鬼荞雏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛平酿,可吹牛的內(nèi)容都是我干的凤优。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蜈彼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筑辨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起幸逆,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤棍辕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后还绘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體楚昭,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年拍顷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抚太。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昔案,死狀恐怖尿贫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踏揣,我是刑警寧澤庆亡,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站呼伸,受9級(jí)特大地震影響身冀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜括享,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望珍促。 院中可真熱鬧铃辖,春花似錦、人聲如沸猪叙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至犬第,卻和暖如春锦积,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歉嗓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工丰介, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鉴分。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓哮幢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親志珍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橙垢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • C語(yǔ)言枚舉 一周七天可以寫(xiě)成 第?個(gè)枚舉成員的默認(rèn)值為整型的 0,后?的枚舉值依次類推伦糯,如果我們想更改柜某,只需要這樣...
    Mjs閱讀 239評(píng)論 0 1
  • C語(yǔ)言的枚舉 C語(yǔ)言的枚舉寫(xiě)法 我們通過(guò)枚舉表示一周的七天 c語(yǔ)言中,枚舉的第一個(gè)成員默認(rèn)是為0敛纲,后面的枚舉值一次...
    浪的出名閱讀 399評(píng)論 0 1
  • 首先感覺(jué)Swift的枚舉確實(shí)相對(duì)OC來(lái)說(shuō)功能和語(yǔ)法都擴(kuò)展很多莺琳。首先說(shuō)一下枚舉的定義。 枚舉聲明的類型是囊括可能狀態(tài)...
    正直走閱讀 296評(píng)論 0 0
  • 鑒于昨天開(kāi)會(huì)部門(mén)會(huì)議討論的時(shí)候载慈,發(fā)現(xiàn)有些朋友對(duì)枚舉的用法還是存在一些疑問(wèn)惭等,所以就寫(xiě)下這個(gè)文章,介紹下Swift下的...
    AndreaArlex閱讀 52,946評(píng)論 14 68
  • Swift中不僅提供了 面向過(guò)程的編程支持办铡,也提供了全面的面向?qū)ο蟮闹С执亲觯c普通的面向?qū)ο蟮木幊陶Z(yǔ)言(只支持類)不...
    隨夢(mèng)而飛飛閱讀 5,180評(píng)論 3 4