swift進階十二:枚舉enum

swift進階 學習大綱

本節,分析枚舉enum

  1. 各語言枚舉區別
  2. swift枚舉的使用
  3. swift枚舉大小
  4. 枚舉的嵌套
  5. 枚舉的遞歸(indirect)
  6. OC橋接
  7. SIL分析

1. 各語言枚舉區別

1.1 C語言枚舉

  • 僅支持Int類型,默認首元素值為0,后續元素值依次+1
    如果中間元素賦值,以賦值為準,后續沒賦值元素值依舊依次+1
enum WEEK {
    Mon, Tue = 10, Wed, Thu, Fri, Sat, Sun
};

enum WEEK a = Mon;
enum WEEK b = Wed;
printf("%d",a);  // 打印0
printf("%d",b);  // 打印11

OC語言枚舉類型C語言一致

1.2 Swift枚舉

十分強大

    1. 格式: 不用逗號分隔,類型需使用case聲明
    1. 內容:
    • 支持Int、Double、String基礎類型,有默認枚舉值
      String類型默認枚舉值key名,Int、Double數值型默認枚舉值0開始+1遞增 )
    • 支持自定義選項
      不指定支持類型沒有rawValue。但同樣支持case枚舉,可自定義關聯內容
  • 指定類型:
    沒指定枚舉值時,各類型都有默認枚舉值。
// Double類型
// CaseIterable協議,有allCases屬性,支持遍歷所有case
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)}
image.png
  • 自定義類型(強大
    不指定枚舉類型,可給枚舉項添加拓展內容switch讀取時,可提取出拓展類型,進行相應操作。
// 自定義類型的使用
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

func printValue(_ v: Shape) {
    // switch區分case(不想每個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)
image.png

2. swift枚舉的使用

  • swift枚舉的讀取,有兩種方式:

1.統一使用switch區分case

  1. 判斷單類case,直接使用if語句

2.1 switch方式

  • 靈活的屬性讀取
  1. let聲明整個case
  2. 分開聲明case中的每個關聯內容
  3. 合并case,使用同一個變量x
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

func printValue1(_ v: Shape) {
    switch v {
    // 1. let聲明整個case
    case let .square(x):
        print(x)
    // 2. 分開聲明case中的每個關聯內容
    case .circle(let x, var y):
        y += 100  // var聲明的變量,可修改和賦值
        print("radius: \(x), borderWidth: \(y)")
    }
}

func printValue2(_ v: Shape) {
    switch v {
    // 3. 合并case,使用同一個變量x
    case let .square(x), let .circle(x, _):
        print(x)
    }
}

let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)

print("------")
printValue1(s)
printValue1(c)

print("------")
printValue2(s)
printValue2(c)
image.png

2.2 if 方式

  • 判斷是否為指定case項,并獲取關聯內容
    (同樣支持整體case關聯內容聲明分開聲明
// 自定義類型的使用
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)

// 判斷s是否是square類型。并獲取`關聯內容`
// 1. 內部聲明關聯內容類型(如: let)
if case .square(let width) = s {
    print(width)
}

// 2. 聲明case所有關聯內容類型(如: var)
if case var .circle(radius, borderWidth) = c {
    radius += 200
    borderWidth += 100
    print(radius, borderWidth)
}
image.png

2.3 計算型屬性 & 函數

  • enum枚舉支持計算型屬性函數
enum Direct: Int {
    case up
    case down
    case left
    case right
    
    // 計算型屬性
    var description: String{
        switch self {
        case .up:
            return "這是上面"
        default:
            return "這是\(self)"
        }
    }
    
    // 函數
    func printSelf() {
        print(description)
    }
}

Direct.down.printSelf() // 打?。?這是down

3. swift枚舉大小

size: 實際占用內存大小
stride:系統分配的內存大小

指定類型:

  • 一個case項:size0高版本xcode可能為1 ),stride1
  • 多個case項:
    case小于255個: size1,stride1
    超過255個會自動擴容sizestride都會增加
    (原因,1字節(8bit)可區分255種情況。所以默認size1,當只有一個case時,0x0可表示。所以size01都可理解)
image.png

自定義:

  • 占用內存空間最大case大小 + enum自身大小:
    image.png

    如果不清楚Foo2case大小size為何為18,可查看內存對齊

順帶提供一個struct(5個屬性值)大小計算方式

MyStruct1 內存計算

4. 枚舉的嵌套

  • 枚舉的嵌套,本質上只是在不同作用域創建,并沒有造成結構上嵌套。

4.1 enum嵌套enum

enum Foo {
    
    enum Direct: Int {
        case up
        case down
        case left
        case right
    }
    
    case leftUp(element1: Direct, element2: Direct)
    case rightDown(element1: Direct, element2: Direct)
}

var f = Foo.leftUp(element1: .left, element2: .up)

4.2 struct嵌套enum

struct Foo {
    
    enum Direct: Int {
        case up
        case down
        case left
        case right
    }
    
    let key: Direct
    
    func printKey() {
        switch key {
        case .up:    print("上")
        case .down:  print("下")
        default:
            print("其他")
        }
    }
    
}

var f = Foo(key: .down)
f.printKey()  // 打?。?下

5. 枚舉的遞歸(indirect)

  • 枚舉中case關聯內容使用自己枚舉類型,是否會造成遞歸枚舉大小如何確定?

案例:

  • 樹節點,需要重復使用:
    image.png
  • 直接使用XCode報錯
    (因為直接使用,enum大小需要case確定,而case大小又需要使用到enum大小。所以無法計算大小,報錯)
  • swift提供了indirect關鍵字,可以標記遞歸枚舉,也可以標記單個case,被標記后,case項直接去堆中申請內存,變為引用類型,大小為指針8字節
image.png
  • 輸出SIL中間代碼,可以看到是使用alloc_box創建枚舉值,內部調用了swift_allocObject,去申請空間:
    image.png

SIL官方文檔中,有介紹alloc_box:

image.png

匯編層也可佐證

image.png

6.OC橋接

  • OC枚舉僅支持Int類型,而swift支持多種類型。

6.1 OC使用swift枚舉:

    1. swift中創建Int類型枚舉值,使用@objc聲明
      image.png
    1. @objc聲明后,橋接文件中,自動生成了OCSWIFT_ENUM:
      image.png
    1. OC文件中,導入swift橋接頭文件,直接調用SwiftEnum
      image.png

6.2 swift使用OC枚舉:

  1. OC.h頭文件聲明枚舉類型:
  • typedef NS_ENUM(NSUInteger, OCEnum):自動轉換成swift枚舉
  • typedef enum:轉換成結構體
    image.png
  1. 橋接文件中,添加OC頭文件:

    image.png

  2. 自動生成swift文件中,可以看到轉換的類型:

    image.png

    image.png

  3. swift文件中使用:

  • NS_ENUM生成:可正常使用
  • typedef enum生成:只能通過通過值初始化,再使用
    image.png

6.3 OC使用swift枚舉:

  • 如果swift不是Int類型,而又希望OC能用,只能自己做個橋接

(例如: 原本swift枚舉類型String,可直接通過rawValue讀取值。
為了兼容OC,把類型改成Int,自定義計算型屬性,禁止使用默認的rawValue讀?。?/p>

  • swift中創建int類型枚舉,自定義string計算屬性,并禁止rawValue的使用。

    image.png

  • OC直接使用

    image.png

  • 如果還希望OC能訪問到swift對應的String值:

使用class的類方法兼容

  • class需要繼承NSObject類函數完成枚舉String的映射。enum禁止rawValue,改用string計算屬性獲取

    image.png

  • 橋接文件中可以看到生成了OC類方法枚舉:

    image.png

    image.png

  • OC文件中使用:

    image.png

7. SIL分析

7.1 enum格式

  • 案例代碼:
enum Week: String {
    case Mon
    case Tue
    case Wed
    case Thu
    case Fri
    case Sat
    case Sun
}
  • 打開終端,cd當前文件夾,swiftc -emit-sil main.swift > ./main.sil命令輸出SIL文件:

取消swift函數名的混淆輸出: swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil

image.png

7.2 rawValue的讀取

  • SIL文件中,搜索rawValuegetter方法:
    switch跳轉指定case,執行函數,得到case內容,返回case內容
    image.png

有2個疑問:

  1. 默認屬性(字符串)是什么時候創建的?
  2. 如何記錄case名對應值的?
  1. 默認屬性(字符串)是什么時候創建的?

編譯期就會生成所有符號

image.png

  • 所以上面rawValue讀取時,可直接通過string_literalMachO讀取字符
  1. 如何記錄case名對應值的?
  • String類型枚舉,caserawValue值(打印結果一樣,但類型是對應枚舉類型String
  • 通過rawValue初始化case時,類型為Option找不到對應case時,為nil
enum Week: String {
   case Mon
   case Tue
   case Wed
   case Thu
   case Fri
   case Sat
   case Sun
}

// case與rawValue值(打印結果一樣,但類型不同)
print("類型:\(type(of: Week.Mon)) 值:\(Week.Mon)")                   // 打印: 類型:Week   值:Mon
print("類型:\(type(of: Week.Mon.rawValue)) 值:\(Week.Mon.rawValue)") // 打?。?類型:String 值:Mon

// 通過rawValue來生成對應的case(可選類型,找不到rawValue對應的case,就是nil)
print(Week.init(rawValue: "Mon"))    // 打印: Optional(Demo.Week.Mon)
print(Week.init(rawValue: "Hello"))  // 打印: nil
  • 通過SIL分析init(rawValue:):

    image.png

    完整流程

  • 1.創建:
    創建枚舉(格式:元組(Array<T>,Pointer),此例中TString) ,再遍歷創建所有枚舉項。

  • 2.查詢:
    通過_findStringSwitchCase 獲取入參值index,使用switch通過index(int類型)匹配到case,匹配成功:返回optional的some值,匹配失敗,直接返回nil

  • swift源碼中搜索findStringSwitchCase,可以看到是通過for遍歷匹配index

    image.png


enum枚舉較為簡單,我們了解了與其他語言差異、用法,順帶探索大小源碼實現

  • 下一節,介紹swift閉包
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容

  • C語言枚舉 一周七天可以寫成 第?個枚舉成員的默認值為整型的 0,后?的枚舉值依次類推,如果我們想更改,只需要這樣...
    Mjs閱讀 241評論 0 1
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,850評論 0 6
  • 枚舉入門 基本定義 枚舉作為 Swift 的 一等類型, 我們正好可以通過枚舉, 來 一一列舉 Swift 其它 ...
    overla5閱讀 287評論 0 2
  • 基本使用 1、從語法上看,對比我們熟悉的其他語言,每個成員前面多了一個 case關鍵字。2、并且跟我們熟悉的另一點...
    swift加oc閱讀 1,812評論 0 1
  • 首先感覺Swift的枚舉確實相對OC來說功能和語法都擴展很多。首先說一下枚舉的定義。 枚舉聲明的類型是囊括可能狀態...
    正直走閱讀 296評論 0 0