Swift進(jìn)階07:枚舉enum

補(bǔ)充:添加腳本自動生成SIL

  • 通過target -> +,選擇 other -> Aggregate帽芽,然后命名為HTScript
image
  • 選中HTScript饿这,選擇Build Phases -> 添加New Run Script Phase
image
  • Run Script中輸入以下命令
swiftc -emit-sil ${SRCROOT}/HTEnumTest/main.swift | xcrun swift-demangle > ./main.sil && "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" main.sil
  • 選中HTScriptBuild油湖,可以自動生成SIL文件(即 main.swift)检激,并且vs code自動打開文件
    image

C中的枚舉

在介紹swift中的枚舉之前肴捉,首先我們來回顧下C中的枚舉寫法,如下所示

enum 枚舉名{
    枚舉值1呵扛,
    枚舉值2每庆,
    ......
};

<!--舉例:表示一周7天-->
enum Weak{
    MON, TUE, WED, THU, FRI, SAT, SUN
};

<!--更改C中枚舉默認(rèn)值-->
//如果沒有設(shè)置枚舉默認(rèn)值,一般第一個枚舉成員的默認(rèn)值為整型0今穿,后面依此類推
enum Weak{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
};

<!--C中定義一個枚舉變量-->
//表明創(chuàng)建了一個枚舉,并聲明了一個枚舉變量weak
enum Weak{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
}weak;
//或者下面這種寫法伦籍,省略枚舉名稱
enum{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
}weak;

Swift中的枚舉

在swift中蓝晒,枚舉的創(chuàng)建方式如下所示,如果沒有指定枚舉值的類型帖鸦,那么enum默認(rèn)枚舉值是整型的

<!--1芝薇、寫法一-->
enum Weak{
    case mon
    case tue
    case wed
    case thu
    case fri
    case sat
    case sun
}

<!--2、寫法二-->
//也可以直接一個case作儿,然后使用逗號隔開
enum Weak{
    case mon, tue, wed, thu, fri, sat, sun
}

<!--定義一個枚舉變量-->
var w: Weak = .mon
  • 如果此時想創(chuàng)建一個枚舉值是String類型的enum洛二,可以通過指定enum的枚舉值的類型來創(chuàng)建,其中枚舉值和原始值rawValue的關(guān)系為case 枚舉值 = rawValue原始值
/*
- =左邊的值是枚舉值,例如 mon
- =右邊的值在swift中稱為 RawValue(原始值)晾嘶,例如 "mon"
- 兩者的關(guān)系為:case 枚舉值 = rawValue原始值
*/
enum Weak: 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 Weak: String {
    case mon, tue, wed = "wed", thu, fri, sat, sun
}
//Int類型垒迂,mon是從0開始依此類推械姻,wed后是從10依此類推
enum Weak: Int {
    case mon, tue, wed = 10, thu, fri, sat, sun
}
image
  • 【注??】:如果enum沒有聲明類型,是沒有rawValue屬性的
image

枚舉的原始值

代碼如下:

enum Weak: String {
    case mon, tue, wed, thu, fri, sat, sun
}

var value = Weak.mon.rawValue
print(value)

//---打印結(jié)果 mon

這里就有一個疑問机断,swift是如何做到打印 mon的楷拳?我們通過SIL文件分析

  • 首先查看SIL文件中的enum,底層多增加了一些東西
    • 1吏奸、給枚舉值的類型欢揖,通過typealias取了一個別名RawValue
    • 2、默認(rèn)添加了一個可選類型的init方法
    • 3奋蔚、增加一個計算屬性rawValue浸颓,用于獲取枚舉值的原始值
image
  • 查看SIL中的main方法,可以得知變量value是通過枚舉值的rawValueget方法獲取
image
  • 查看SIL文件rawValueget方法旺拉,主要有以下幾步:
    • 1产上、接收一個枚舉值,用于匹配對應(yīng)的分支
    • 2蛾狗、在對應(yīng)分支創(chuàng)建對應(yīng)的String
    • 3晋涣、返回對應(yīng)的String
image

image

結(jié)論1:使用rawValue的本質(zhì)是調(diào)用get方法
但是get方法中的String是從哪里來的呢?String存儲在哪里沉桌?

  • 其實(shí)這些對應(yīng)分支的字符串在編譯時期就已經(jīng)存儲好了谢鹊,即存放在Maach-O文件的__TEXT.cstring中,且是連續(xù)的內(nèi)存空間留凭,可以通過編譯后查看Mach-O文件來驗證
image

結(jié)論2rawValueget方法中的分支構(gòu)建的字符串佃扼,主要是從Mach-O文件對應(yīng)地址取出的字符串,然后再返回給value

總結(jié)

  • 使用rawValue的本質(zhì)就是在底層調(diào)用get方法蔼夜,即在get方法中從Mach-O對應(yīng)地址中取出字符串并返回的操作

區(qū)分 case枚舉值 & rawValue原始值

請問下面這段代碼的打印結(jié)果是什么兼耀?

//輸出 case枚舉值
print(Weak.mon)
//輸出 rawValue
print(Weak.mon.rawValue)

//---打印結(jié)果
mon
mon

雖然這兩個輸出的值從結(jié)果來看是沒有什么區(qū)別的,雖然輸出的都是mon求冷,但并不是同一個東西

  • 第一個輸出的case枚舉值
  • 第二個是通過rawValue訪問的rawValueget方法

如果我們像下面這種寫法瘤运,編譯器就會報錯


image

枚舉的init初始化

主要是探索枚舉的init會在什么時候調(diào)用

  • 定義一個符號斷點(diǎn)Weak.init
image

定義如下代碼

enum Weak: String {
    case mon, tue, wed, thu, fri, sat, sun
}

var w = Weak.init(rawValue: "mon")
var sun = Weak(rawValue: "mon")

運(yùn)行發(fā)現(xiàn),這兩種初始化方法都會觸發(fā)調(diào)試斷點(diǎn)

image

總結(jié)enuminit方法的調(diào)用是通過枚舉.init(rawValue:)或者枚舉(rawValue:)觸發(fā)的

  • 我們再繼續(xù)來分析init方法匠题,來看下面這段代碼的打印結(jié)果是什么拯坟?
var w = Weak.init(rawValue: "mon")
var sun = Weak(rawValue: "sunday")
print(w)
print(sun)

image

從運(yùn)行結(jié)果可以看出,第一個輸出的是可選值韭山;第二個輸出的是nil郁季,表示沒有找到對應(yīng)的case枚舉值冷溃。為什么會出現(xiàn)這樣的情況呢?

  • 首先分析SIL文件中的Weak.init方法梦裂,主要有以下幾步:
    • 1似枕、在init方法中是將所有enum的字符串從Mach-O文件中取出,依次放入數(shù)組中
    • 2塞琼、放完后菠净,然后調(diào)用_findStringSwitchCase方法進(jìn)行匹配
image

image

image

其中 index_addr 表示獲取當(dāng)前數(shù)組中的第n個元素值的地址,然后再把構(gòu)建好的字符串放到當(dāng)前地址中

- `struct_extract` 表示`取出當(dāng)前的Int值`彪杉,Int類型在系統(tǒng)中也是結(jié)構(gòu)體
- `cond_br` 表示比較的表達(dá)式毅往,即分支條件跳轉(zhuǎn)
    - 如果匹配成功,則構(gòu)建一個`.some的Optional`返回
    - 如果匹配不成功派近,則繼續(xù)匹配攀唯,知道最后還是沒有匹配上,則構(gòu)建一個`.none的Optional`返回
  • swift-source中查找_findStringSwitchCase方法渴丸,接收兩個參數(shù)侯嘀,分別是 數(shù)組 + 需要匹配的String
    • 1、遍歷數(shù)組谱轨,如果匹配則返回對應(yīng)的index
    • 2戒幔、如果不匹配,則返回-1
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
// 接收一個數(shù)組 + 需要匹配的string
func _findStringSwitchCase( 
  cases: [StaticString],
  string: String) -> Int {
// 遍歷之前創(chuàng)建的字符串?dāng)?shù)組土童,如果匹配則返回對應(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
}

枚舉的遍歷

  • 對于某些枚舉來說,如果能有一個集合包含了枚舉的所有情況就好了献汗。你可以通過在枚舉名字后面寫 : CaseIterable 來允許枚舉被遍歷敢订。Swift 會暴露一個包含對應(yīng)枚舉類型所有情況的集合名為 allCases

CaseIterable協(xié)議通常用于沒有關(guān)聯(lián)值的枚舉,用來訪問所有的枚舉值罢吃,只需要對應(yīng)的枚舉遵守該協(xié)議即可楚午,然后通過allCases獲取所有枚舉值,如下所示

//1尿招、定義無關(guān)聯(lián)值枚舉矾柜,并遵守協(xié)議
enum Weak: String {
    case mon, tue, wed, thu, fri, sat, sun
}
extension Weak: CaseIterable {}

//2、通過for循環(huán)遍歷
for oneCase in Weak.allCases {
    print(oneCase)
}

//3泊业、通過函數(shù)式編程遍歷
let allCase = Weak.allCases.map { $0.rawValue }.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á)一個形狀,其中有圓形租谈、長方形等篮奄,圓形有半徑捆愁,長方形有寬、高窟却,我們可以通過下面具有關(guān)聯(lián)值的enum來表示

//注:當(dāng)使用了關(guān)聯(lián)值后昼丑,就沒有RawValue了,主要是因為case可以用一組值來表示夸赫,而rawValue是單個的值
enum Shape{
    //case枚舉值后括號內(nèi)的就是關(guān)聯(lián)值菩帝,例如 radius
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
}

【注??】具有關(guān)聯(lián)值的枚舉,就沒有rawValue屬性了茬腿,主要是因為一個case可以用一個或者多個值來表示呼奢,而rawValue只有單個的值

這一點(diǎn)我們也可以通過SIL文件 來驗證

  • 首先查看SIL文件,發(fā)現(xiàn)此時的enum中既沒有別名切平,也沒有init方法握础、計算屬性rawValue
image
  • 其中關(guān)聯(lián)值中radiuswidth悴品、height這些都是自定義的標(biāo)簽禀综,也可以不寫,如下所示苔严,但并不推薦這種方式定枷,因為可讀性非常差
enum Shape{
    //case枚舉值后括號內(nèi)的就是關(guān)聯(lián)值,例如 radius
    case circle(Double)
    case rectangle(Int, Int)
}
  • 關(guān)聯(lián)值的枚舉值的創(chuàng)建
//創(chuàng)建
var circle = Shape.circle(radius: 10.0)
//重新分配
circle = Shape.rectangle(width: 10, height: 10)

模式匹配

enum中的模式匹配其實(shí)就是匹配case枚舉值

簡單enum的模式匹配

【注】:swift中的enum模式匹配需要將所有情況都列舉届氢,或者使用default表示默認(rèn)情況欠窒,否則會報錯

enum Weak: String {
    case mon, tue, wed, thu, fri, sat, sun
}

var w: Weak?

switch w {
    case .mon:
        print(Weak.mon.rawValue)
    case .tue:
        print(Weak.tue.rawValue)
    default:
        print("unknow day")
}

//---打印結(jié)果 unknow day

查看其SIL文件,其內(nèi)部是將nil放入w全局變量悼沈,然后匹配case贱迟,做對應(yīng)的代碼跳轉(zhuǎn)

image

具有關(guān)聯(lián)值enum的模式匹配

關(guān)聯(lián)值的模式匹配主要有兩種:

  • 1、通過switch匹配所有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)")
}

//---打印結(jié)果: circle radius: 10.0
  • 2絮供、也可以這么寫,將關(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)建一個關(guān)聯(lián)值的元組
  • 2、根據(jù)當(dāng)前case枚舉值贮乳,匹配對應(yīng)的case忧换,并跳轉(zhuǎn)
  • 3、取出元組中的值向拆,將其賦值給匹配case中的參數(shù)
image
  • 通過if case匹配單個case亚茬,如下所示
enum Shape{
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
}

let shape = Shape.circle(radius: 10.0)
//匹配單個case
if case let Shape.circle(radius) = shape {
    print("circle radius: \(radius)")
}

//---打印結(jié)果: circle radius: 10.0
  • 如果我們只關(guān)心不同case的相同關(guān)聯(lián)值(即關(guān)心不同case的某一個值),需要使用同一個參數(shù)浓恳,例如案例中的x刹缝,如果分別使用x碗暗、y, 編譯器會報錯
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
}

枚舉的嵌套

枚舉的嵌套主要用于以下場景:

  • 1梢夯、【枚舉嵌套枚舉】一個復(fù)雜枚舉是由一個或多個枚舉組成
  • 2言疗、【結(jié)構(gòu)體嵌套枚舉】enum是不對外公開的,即是私有的

枚舉嵌套枚舉

枚舉嵌套枚舉颂砸,實(shí)現(xiàn)方向的組合噪奄,如下圖所示

enum CombineDirect{
    //枚舉中嵌套的枚舉
    enum BaseDirect{
        case up
        case down
        case left
        case right
    }
    //通過內(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)
print(leftUp)
image

結(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中只能包含計算屬性類型屬性人乓,不能包含存儲屬性

enum Shape{
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
    
    //編譯器報錯:Enums must not contain stored properties 不能包含存儲屬性勤篮,因為enum本身是值類型
//    var radius: Double
    
    //計算屬性 - 本質(zhì)是方法(get、set方法)
    var with: Double {
        get {
            return 10.0
        }
    }
    //類型屬性 - 是一個全局變量
    static let height = 20.0
}

為什么struct中可以放存儲屬性撒蟀,而enum不可以叙谨?
主要是因為struct中可以包含存儲屬性是因為其大小就是存儲屬性的大小。而對enum來說就是不一樣的保屯,enum枚舉的大小是取決于case的個數(shù)的手负,如果沒有超過255,enum的大小就是1字節(jié)(8位)

枚舉中包含方法

可以在enum中定義實(shí)例方法姑尺、static修飾的方法

enum Weak: Int {
    case mon, tue, wed, thu, fri, sat, sun
    
    mutating func nextDay() {
        self = Weak.init(rawValue: self.rawValue + 1) ?? Weak.mon
    }
}

//使用
var w = Weak.mon
w.nextDay()
print(w)

indirect關(guān)鍵字

如果我們想要表達(dá)的enum是一個復(fù)雜的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)時竟终,可以通過indirect關(guān)鍵字來讓當(dāng)前的enum更簡潔

//用枚舉表示鏈表結(jié)構(gòu)
enum List<T>{
    case end
    //表示case使是引用來存儲
    indirect case node(T, next: List<T>)
}

<!--也可以將indirect放在enum前-->
//表示整個enum是用引用來存儲
indirect enum List<T>{
    case end
    case node(T, next: List<T>)
}

為什么呢?

  • 因為enum值類型切蟋,也就意味著他們的大小在編譯時期就確定了统捶,那么這個過程中對于當(dāng)前的enum的大小是不能確定的,從系統(tǒng)的角度來說柄粹,不知道需要給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的大小
image

如果傳入的類型是String呢?


image

從結(jié)果發(fā)現(xiàn)驻右,換成其他類型什黑,其結(jié)果依舊是8,這是為什么呢堪夭?

下面來分析其內(nèi)存結(jié)構(gòu)愕把,首先需要定義一個全局變量

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))

通過lldb分析其內(nèi)存


image

所以indirect關(guān)鍵字其實(shí)就是通知編譯器,我當(dāng)前的enum是遞歸的森爽,大小是不確定的恨豁,需要分配一塊堆區(qū)的內(nèi)存空間,用來存放enum

  • 如果是end爬迟,此時存儲的是case值橘蜜,而case為nodeindirect修飾)時存儲的是引用地址
image
  • 通過SIL來驗證
image

swift和OC混編enum

在swift中,enum非常強(qiáng)大付呕,可以添加方法扮匠、添加extension
而在OC中捧请,enum僅僅只是一個整數(shù)值

如果想將swift中的enum暴露給OC使用:

  • @objc關(guān)鍵字標(biāo)記enum
  • 當(dāng)前enum應(yīng)該是Int類型

OC調(diào)用Swift的enum

<!--swift中定義-->
@objc enum Weak: Int {
    case mon, tue, wed, thu, fri, sat, sun
}

<!--OC使用-->
- (void)test {
    Weak mon = WeakMon;
}
  • 枚舉必須用@objc修飾凡涩,必須是Int類型
image
image
image

Swift調(diào)用OC的enum

OC中的枚舉會自動轉(zhuǎn)換成swift中的enum

<!--OC定義-->
//會自動轉(zhuǎn)換成swift的enum
NS_ENUM(NSInteger, OCENUM){
    Value1,
    Value2
};

<!--swift使用-->
//1棒搜、將OC頭文件導(dǎo)入橋接文件
#import "HTOcTest.h"
//2、使用
let ocEnum = OCENUM.Value1
  • 如果OC中是使用typedef enum定義的活箕,自動轉(zhuǎn)換成swift就成了下面這樣
typedef enum {
    Num1,
    Num2
}OCNum;

<!--swift中使用-->
let typeEnum = OCNum.init(rawValue: 1)
print(typeEnum)

//*******打印結(jié)果*******
OCNum(rawValue: 0)

問題:OC如何訪問swift中String類型的enum力麸?

  • swift中的enum盡量聲明成Int整型
  • 然后OC調(diào)用時,使用的是Int整型的
  • enum在聲明一個變量/方法育韩,用于返回固定的字符串克蚂,用于在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

1赤屋、普通enum大小分析

enum中不能包含存儲屬性,其根本在于enum的大小與Struct的計算方式是不一樣的壁袄,這里我們將展開詳細(xì)的分析

  • 首先类早,我們先來看看下面這段代碼的打印結(jié)果是什么?
enum NoMean{
    case a
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印結(jié)果-->
0 //size大小是0
1 //表示訪問下一個NoMean的case時嗜逻,需要跨越1字節(jié)的步長
  • 如果此時增加一個 case b涩僻,此時的打印結(jié)果是什么?
enum NoMean{
    case a
    case b
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印結(jié)果-->
1 //size大小是1
1 //步長是1
  • 如果再增加多個呢栈顷?
enum NoMean{
    case a
    case b
    case c
    case d
}
print(MemoryLayout<NoMean>.size)
print(MemoryLayout<NoMean>.stride)

<!--打印結(jié)果-->
1
1
  • 如果繼續(xù)增加 case的個數(shù)超過 256
image

從上面結(jié)果分析得出:

  • case默認(rèn)是UInt8逆日,即1字節(jié)(8位)最大可以存儲256個case
  • 如果 case個數(shù)超過 256萄凤,會自動從 UInt8 -> UInt16 -> UInt32 -> UInt64 ...

LLDB分析

  • 分別定義4個全局變量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

通過lldb查看內(nèi)存情況如下狠半,case都是1字節(jié)大小


image

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的步長是24

說明從打印結(jié)果可以說明 enum中有關(guān)聯(lián)值時行嗤,其內(nèi)存大小取決于關(guān)聯(lián)值的大小

  • enum有關(guān)聯(lián)值時已日,關(guān)聯(lián)值的大小 取 對應(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é)對齊护奈,所以自動補(bǔ)齊到24
  • 定義一個全局變量缔莲,觀察其內(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 表示 對齊后的大小(內(nèi)存空間中真實(shí)占用的大型疑痢)

3檐晕、enum嵌套enum的大小分析

請問下面這段代碼的打印結(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)值的大小辟灰,每個case都有2個大小為1的enum,加上case枚舉值 應(yīng)該是 3啦逆,這里編譯器做了優(yōu)化伞矩,所以為2
2 //stride大小
image

總結(jié)

  • enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小
  • 當(dāng)嵌套enum的case只有2個時,case在內(nèi)存中的存儲是0夏志、8
  • 當(dāng)嵌套enum的case大于2乃坤,小于等于4時,case在內(nèi)存中的存儲是 0沟蔑、4湿诊、8、c
  • 當(dāng)嵌套enum的case大于4時瘦材,case在內(nèi)存中的存儲是從0厅须、1、2...以此類推

4食棕、結(jié)構(gòu)體嵌套enum的大小分析

請問下面這段代碼的打印結(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,沒有聲明變量簿晓,結(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中目前沒有屬性
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
  • 如果在增加一個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大谐(與OC中的結(jié)構(gòu)體大小計算是一致的耀里,min(m,n),其中m表示存儲的位置,n表示屬性的大小拾氓,要求是:m必須整除n)
16 //stride大小

總結(jié)

  • 1冯挎、如果結(jié)構(gòu)體中沒有其他屬性,只有枚舉變量痪枫,那么結(jié)構(gòu)體的大小就是枚舉的大小织堂,即size為1
  • 2、如果結(jié)構(gòu)體中嵌套了enum奶陈,但是沒有聲明變量,此時的size是0附较,stride是1
  • 3吃粒、如果結(jié)構(gòu)體中還有其他屬性,則按照OC中的結(jié)構(gòu)體內(nèi)存對齊三原則進(jìn)行分析

內(nèi)存對齊 & 字節(jié)對齊 區(qū)分

  • 內(nèi)存對齊:iOS中是8字節(jié)對齊拒课,蘋果實(shí)際分配采用16字節(jié)對齊徐勃,這種只會在分配對象時出現(xiàn)
  • 字節(jié)對齊:存儲屬性的位置必須是偶地址,即OC內(nèi)存對齊中的min(m早像,n)僻肖,其中m表示存儲的位置,n表示屬性的大小卢鹦,需要滿足位置m整除n時臀脏,才能從該位置存放屬性。簡單來說冀自,就是必須在自身的倍數(shù)位置開始
  • 外部調(diào)用對象時揉稚,對象是服從內(nèi)存對齊
  • 單純從結(jié)構(gòu)上說熬粗,結(jié)構(gòu)內(nèi)部服從最大字節(jié)對齊搀玖。

例如下面這個例子

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驻呐,下一個位置是9灌诅,但是UInt16是2字節(jié)對齊的要在它的倍數(shù)位置開始所以找下一個可以整除它的位置也就是UInt16占據(jù)10-11正好整個size在0-11,所以size為12
  • stride為16的原因:stride是實(shí)際分配的含末,必須是最大屬性大小的整數(shù)倍猜拾,即8的倍數(shù),所以是16

總結(jié)

  • 枚舉說明:
    • 1答渔、enum中使用rawValue的本質(zhì)是調(diào)用get方法关带,即在get方法中從Mach-O對應(yīng)地址中取出字符串并返回的操作
    • 2、enum中init方法的調(diào)用是通過枚舉.init(rawValue:)或者枚舉(rawValue:)觸發(fā)的
    • 3、沒有關(guān)聯(lián)值的enum宋雏,如果希望獲取所有枚舉值芜飘,需要遵循CaseIterable協(xié)議,然后通過枚舉名.allCase的方式獲取
    • 4磨总、case枚舉值和rawValue原始值的關(guān)系:case 枚舉值 = rawValue原始值
    • 5嗦明、enum的模式匹配方式,主要有兩種:switch / if case
    • 6、enum可以嵌套enum毫痕,也可以在結(jié)構(gòu)體中嵌套enum虑瀑,表示該enum是struct私有的
    • 7、enum中還可以包含計算屬性诗良、類型屬性,但是不能包含存儲屬性
    • 8鲁驶、enum中可以定義實(shí)例 + static修飾的方法
  • 枚舉內(nèi)存大小
    • 1鉴裹、普通enum的內(nèi)存大小一般是1字節(jié),如果只有一個case钥弯,則為0径荔,表示沒有意義,如果case個數(shù)超過256脆霎,則枚舉值的類型由UInt8->UInt16->UInt32...
    • 2总处、具有關(guān)聯(lián)值的enum大小,取決于最大case的內(nèi)存大小+case的大芯χ搿(1字節(jié))
    • 3鹦马、enum嵌套enum同樣取決于最大case的關(guān)聯(lián)值大小
    • 4、結(jié)構(gòu)體嵌套enum玖院,如果沒有屬性菠红,則size為0,如果只有enum屬性难菌,size為1试溯,如果還有其他屬性,則按照OC中內(nèi)存對齊原則進(jìn)行計算
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郊酒,一起剝皮案震驚了整個濱河市遇绞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燎窘,老刑警劉巖摹闽,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異褐健,居然都是意外死亡付鹿,警方通過查閱死者的電腦和手機(jī)澜汤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舵匾,“玉大人俊抵,你說我怎么就攤上這事∽荩” “怎么了徽诲?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吵血。 經(jīng)常有香客問我谎替,道長,這世上最難降的妖魔是什么蹋辅? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任钱贯,我火速辦了婚禮,結(jié)果婚禮上晕翠,老公的妹妹穿的比我還像新娘喷舀。我一直安慰自己,他們只是感情好淋肾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爸邢,像睡著了一般樊卓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杠河,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天碌尔,我揣著相機(jī)與錄音,去河邊找鬼券敌。 笑死唾戚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的待诅。 我是一名探鬼主播叹坦,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卑雁!你這毒婦竟也來了募书?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤测蹲,失蹤者是張志新(化名)和其女友劉穎莹捡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扣甲,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡篮赢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片启泣。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡涣脚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出种远,到底是詐尸還是另有隱情涩澡,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布坠敷,位于F島的核電站妙同,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏膝迎。R本人自食惡果不足惜粥帚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望限次。 院中可真熱鬧芒涡,春花似錦、人聲如沸卖漫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羊始。三九已至旱幼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間突委,已是汗流浹背柏卤。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匀油,地道東北人缘缚。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像敌蚜,于是被迫代替她去往敵國和親桥滨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

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