Swift底層進(jìn)階--010:枚舉

C語(yǔ)?枚舉

先來(lái)回顧?下C語(yǔ)?的枚舉寫法:

enum 枚舉名 {
    枚舉值1,
    枚舉值2,
    ......
};

?如表示?周 7天,?C語(yǔ)?的枚舉寫法應(yīng)該是這樣的:

enum week {
    MON, TUE, WED, THU, FRI, SAT, SUN
};

第?個(gè)枚舉成員默認(rèn)值為0平窘,后?枚舉值依次類推。如果更改只需這樣操作:

enum week {
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
};

定義?個(gè)枚舉變量week

enum Week {
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
} week;

同樣是定義?個(gè)枚舉變量week,省略枚舉名稱:

enum {
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
} week;
Swift中的枚舉
枚舉基礎(chǔ)

?如表示?周 7天,?Swift的枚舉寫法應(yīng)該是這樣的:

enum Week {
    case MONDAY
    case TUEDAY
    case WEDDAY
    case THUDAY
    case FRIDAY
    case SATDAY
    case SUNDAY
}

上述代碼也可以直接?個(gè)case夭咬,然后?逗號(hào)隔開(kāi):

enum Week {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

Swift中可以創(chuàng)建String類型枚舉。在Swift=左邊的值叫枚舉值铆隘,右邊的叫rawValue原始值卓舵,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"
}

如果不想寫后?的字符串,這時(shí)可以使?隱?rawValue分配膀钠。未指定類型的枚舉掏湾,使用rawValue屬性,編譯報(bào)錯(cuò)

未指定類型的枚舉

Int類型枚舉肿嘲,枚舉值FRI分配rawValue10融击,MON依然是從0開(kāi)始,后?枚舉值依次類推雳窟。在FRI之后的枚舉值從11開(kāi)始尊浪,依次類推

enum Week: Int {
    case MON, TUE, WED, THU, FRI = 10, SAT, SUN
}

print("MON:\(Week.MON.rawValue),SAT:\(Week.SAT.rawValue)")

//輸出以下內(nèi)容:
//MON:0,SAT:11

String類型枚舉,枚舉值FRI分配rawValue打印Hello封救,其他未分配rawValue打印自身枚舉值

enum Week: String {
    case MON, TUE, WED, THU, FRI = "Hello", SAT, SUN
}

print("MON:\(Week.MON.rawValue),FRI:\(Week.FRI.rawValue),SUN:\(Week.SUN.rawValue)")

//輸出以下內(nèi)容:
//MON:MON,FRI:Hello,SUN:SUN

通過(guò)SIL代碼拇涤,分析String類型枚舉,是如何打印rawValue

enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

let w: Week = .MON

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

enum Week : String {
  case MON, TUE, WED, THU, FRI, SAT, SUN
  typealias RawValue = String
  init?(rawValue: String)
  var rawValue: String { get }
}

SIL代碼的枚舉聲明除了case還多了一些東西:

  • 首先通過(guò)typealias取別名誉结,在枚舉Week里把String取名為RawValue
  • 生成可選的初始化方法init?(rawValue: String)工育,也就是說(shuō)初始化可以返回nil
  • 生成rawValue計(jì)算屬性,所以在代碼中訪問(wèn)rawValue屬性搓彻,本質(zhì)就是訪問(wèn)它的get方法
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.w : Swift.String                     // id: %2
  %3 = global_addr @main.w : Swift.String : $*String      // user: %8
  %4 = metatype $@thin Week.Type
  %5 = enum $Week, #Week.MON!enumelt              // user: %7
  // function_ref Week.rawValue.getter
  %6 = function_ref @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String // user: %7
  %7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
  store %7 to %3 : $*String                       // id: %8
  %9 = integer_literal $Builtin.Int32, 0          // user: %10
  %10 = struct $Int32 (%9 : $Builtin.Int32)       // user: %11
  return %10 : $Int32                             // id: %11
} // end sil function 'main'

main方法:

  • %5接收枚舉值
  • %6獲取Week.rawValue.getter方法地址
  • 通過(guò)apply調(diào)用getter方法%6,傳入?yún)?shù)枚舉值%5嘱朽,將返回值賦值給%7
  • 將返回結(jié)果%7存儲(chǔ)到%3
// Week.rawValue.getter
sil hidden @main.Week.rawValue.getter : Swift.String : $@convention(method) (Week) -> @owned String {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Week):
  debug_value %0 : $Week, let, name "self", argno 1 // id: %1
  switch_enum %0 : $Week, case #Week.MON!enumelt: bb1, case #Week.TUE!enumelt: bb2, case #Week.WED!enumelt: bb3, case #Week.THU!enumelt: bb4, case #Week.FRI!enumelt: bb5, case #Week.SAT!enumelt: bb6, case #Week.SUN!enumelt: bb7 // id: %2

bb1:                                              // Preds: bb0
  %3 = string_literal utf8 "MON"                  // user: %8
  %4 = integer_literal $Builtin.Word, 3           // user: %8
  %5 = integer_literal $Builtin.Int1, -1          // user: %8
  %6 = metatype $@thin String.Type                // user: %8
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %7 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
  %8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
  br bb8(%8 : $String)                            // id: %9

bb2:                                              // Preds: bb0
  %10 = string_literal utf8 "TUE"                 // user: %15
  %11 = integer_literal $Builtin.Word, 3          // user: %15
  %12 = integer_literal $Builtin.Int1, -1         // user: %15
  %13 = metatype $@thin String.Type               // user: %15
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %14 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %15
  %15 = apply %14(%10, %11, %12, %13) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %16
  br bb8(%15 : $String)                           // id: %16

bb3:                                              // Preds: bb0
  %17 = string_literal utf8 "WED"                 // user: %22
  %18 = integer_literal $Builtin.Word, 3          // user: %22
  %19 = integer_literal $Builtin.Int1, -1         // user: %22
  %20 = metatype $@thin String.Type               // user: %22
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %21 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %22
  %22 = apply %21(%17, %18, %19, %20) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %23
  br bb8(%22 : $String)                           // id: %23

bb4:                                              // Preds: bb0
  %24 = string_literal utf8 "THU"                 // user: %29
  %25 = integer_literal $Builtin.Word, 3          // user: %29
  %26 = integer_literal $Builtin.Int1, -1         // user: %29
  %27 = metatype $@thin String.Type               // user: %29
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %28 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %29
  %29 = apply %28(%24, %25, %26, %27) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %30
  br bb8(%29 : $String)                           // id: %30

bb5:                                              // Preds: bb0
  %31 = string_literal utf8 "FRI"                 // user: %36
  %32 = integer_literal $Builtin.Word, 3          // user: %36
  %33 = integer_literal $Builtin.Int1, -1         // user: %36
  %34 = metatype $@thin String.Type               // user: %36
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %35 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %36
  %36 = apply %35(%31, %32, %33, %34) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %37
  br bb8(%36 : $String)                           // id: %37

bb6:                                              // Preds: bb0
  %38 = string_literal utf8 "SAT"                 // user: %43
  %39 = integer_literal $Builtin.Word, 3          // user: %43
  %40 = integer_literal $Builtin.Int1, -1         // user: %43
  %41 = metatype $@thin String.Type               // user: %43
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %42 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %43
  %43 = apply %42(%38, %39, %40, %41) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %44
  br bb8(%43 : $String)                           // id: %44

bb7:                                              // Preds: bb0
  %45 = string_literal utf8 "SUN"                 // user: %50
  %46 = integer_literal $Builtin.Word, 3          // user: %50
  %47 = integer_literal $Builtin.Int1, -1         // user: %50
  %48 = metatype $@thin String.Type               // user: %50
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %49 = function_ref @Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %50
  %50 = apply %49(%45, %46, %47, %48) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %51
  br bb8(%50 : $String)                           // id: %51

// %52                                            // user: %53
bb8(%52 : $String):                               // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
  return %52 : $String                            // id: %53
} // end sil function 'main.Week.rawValue.getter : Swift.String'

Week.rawValue.getter方法:

  • bb0旭贬,接收一個(gè)枚舉值,self就是該枚舉值搪泳,通過(guò)switch_enum匹配跳轉(zhuǎn)到對(duì)應(yīng)bb1bb7代碼分支
  • bb1bb7稀轨,構(gòu)建出對(duì)應(yīng)枚舉值的字符串,最終都調(diào)用bb8
  • bb8返回對(duì)應(yīng)字符串

rawValue.getter返回的字符串在編譯時(shí)期已經(jīng)存儲(chǔ)好岸军,通過(guò)Mach-O文件查看它們的存儲(chǔ)位置

Mach-O

rawValue的字符串存儲(chǔ)在__TEXT.__cstring段奋刽,而且內(nèi)存地址是連續(xù)的。在上述SIL代碼中構(gòu)建枚舉值字符串艰赞,本質(zhì)就是從Mach-O里把對(duì)應(yīng)地址的字符串取出來(lái)

區(qū)分case和rawValue
enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

print("case枚舉值:\(Week.MON)")
print("rawValue原始值:\(Week.MON.rawValue)")

//輸出以下內(nèi)容:
//case枚舉值:MON
//rawValue原始值:MON

上述代碼中佣谐,case枚舉值Week.MONrawValue原始值Week.MON.rawValue打印出的內(nèi)容完全一致,但一個(gè)是輸出枚舉值方妖,一個(gè)是訪問(wèn)rawValueget方法狭魂,它們是完全不同的東西

編譯報(bào)錯(cuò)
做一個(gè)簡(jiǎn)單的測(cè)試,如果將String類型的Week.MON.rawValue賦值給Week類型week1,再將Week類型Week.MON賦值給String類型week2雌澄,都會(huì)編譯報(bào)錯(cuò)

init方法的調(diào)用時(shí)機(jī)

添加符號(hào)斷點(diǎn)Week.init斋泄,預(yù)期斷住枚舉初始化方法

Symbol

enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

let week: Week = Week.MON

print("case枚舉值:\(week)")
print("rawValue原始值:\(week.rawValue)")

//輸出以下內(nèi)容:
//case枚舉值:MON
//rawValue原始值:MON

運(yùn)行上述代碼,沒(méi)有達(dá)到預(yù)期镐牺,沒(méi)有任何斷點(diǎn)被觸發(fā)

enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

print(Week(rawValue: "MON"))
print(Week.init(rawValue: "TUE"))
print(Week(rawValue: "Hello"))

//輸出以下內(nèi)容:
//Optional(LGSwiftTest.Week.MON)
//Optional(LGSwiftTest.Week.TUE)
//nil

修改上述代碼炫掐,使用Week(rawValue:)Week.init(rawValue:)成功觸發(fā)斷點(diǎn)

Week.init

繼續(xù)執(zhí)行代碼,從運(yùn)行結(jié)果來(lái)看睬涧,前兩個(gè)打印Optional可選值募胃,第三個(gè)打印nil,因?yàn)檎也坏綄?duì)應(yīng)Hello的枚舉值

運(yùn)行結(jié)果

通過(guò)SIL代碼宙地,分析枚舉的初始化構(gòu)造函數(shù)

enum Week: String {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

print(Week(rawValue: "Hello"))

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

來(lái)到Week.init(rawValue:)方法

init

來(lái)到bb1方法

bb1

bb1bb14摔认,將所有字符串存儲(chǔ)到數(shù)組內(nèi),進(jìn)行case匹配

bb14

通過(guò)源碼查看_findStringSwitchCase方法宅粥,兩個(gè)入?yún)⒎謩e是數(shù)組和需要匹配的字符串参袱,然后遍歷數(shù)組,如果匹配成功返回對(duì)應(yīng)index秽梅,如果匹配失敗返回-1

_findStringSwitchCase

bb15匹配成功抹蚀,直接跳轉(zhuǎn)bb29bb16匹配失敗企垦,繼續(xù)后續(xù)的匹配

bb15环壤、bb16

bb28全部匹配失敗,構(gòu)建一個(gè).none類型的Optional返回钞诡,就是nil郑现。bb29匹配成功,構(gòu)建一個(gè).some類型的Optional返回荧降,就是對(duì)應(yīng)的枚舉值

bb28接箫、bb29

關(guān)聯(lián)值枚舉

如果想?枚舉表達(dá)更復(fù)雜的信息,?不僅僅是?個(gè)rawValue這么簡(jiǎn)單朵诫,這個(gè)時(shí)候可以使?關(guān)聯(lián)值

enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

關(guān)聯(lián)值枚舉辛友,沒(méi)有rawValue屬性。因?yàn)殛P(guān)聯(lián)值枚舉可以使用單個(gè)值或一組值來(lái)表示剪返,但rawValue只能針對(duì)單個(gè)值

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

關(guān)聯(lián)值枚舉
只有case和關(guān)聯(lián)值废累,沒(méi)有typealias取別名,沒(méi)有init方法脱盲,沒(méi)有rawValue計(jì)算屬性

關(guān)聯(lián)值枚舉可以省略關(guān)聯(lián)值的標(biāo)簽邑滨,例如radiouswidth钱反、height驼修,但并不推薦這種書(shū)寫方式殿遂,因?yàn)榭勺x性太差

enum Shape {
    case circle(Double)
    case rectangle(Int, Int)
}

關(guān)聯(lián)值枚舉的使用

//創(chuàng)建
var circle = Shape.circle(radious: 10.0)
//重新分配
circle=Shape.circle(radious: 20.0)
模式匹配

使?switch匹配enum的時(shí)候,必須列舉當(dāng)前所有可能的情況乙各,否則編譯報(bào)錯(cuò)

編譯報(bào)錯(cuò)

匹配enum可以列舉出所有情況墨礁,也可以使用default表示默認(rèn)情況

enum Week: String {
    case MON = "MON"
    case TUE = "TUE"
    case WED = "WED"
    case THU = "THU"
    case FRI = "FRI"
    case SAT = "SAT"
    case SUN = "SUN"
}

var week: Week?

switch week {
    case .MON:
        print(Week.MON.rawValue)
    default:
        print("unknow day")
}

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

SIL

匹配關(guān)聯(lián)值枚舉
  • 方式一:通過(guò)switch匹配所有case
enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

var shape = Shape.circle(radious: 10.0)

switch shape {
    case let .circle(radious):
        print("circle-radious:\(radious)")
    case let .rectangle(width, height):
        print("rectangle-width:\(width),height:\(height)")
}

//輸出以下內(nèi)容:
//circle-radious:10.0

case let .circle(radious)相當(dāng)于做了value-binding耳峦,如果case匹配上恩静,相當(dāng)于把10.0賦值給常量radious

另一種寫法:將關(guān)聯(lián)值的參數(shù)使用letvar修飾

switch shape {
    case .circle(var radious):
        print("circle-radious:\(radious)")
    case .rectangle(let width, let height):
        print("rectangle-width:\(width)蹲坷,height:\(height)")
}

通過(guò)SIL代碼驶乾,查看關(guān)聯(lián)值枚舉的匹配模式

enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

var shape = Shape.circle(radious: 10.0)

var temR: Double
var w: Int
var h: Int

switch shape {
    case .circle(let radious):
        temR=radious
    case .rectangle(let width, let height):
        w=width
        h=height
}

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main

bb1

  • 方式二:通過(guò)單個(gè)case進(jìn)行匹配
enum Shape {
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

var shape = Shape.circle(radious: 10.0)

if case let Shape.circle(radious) = shape {
    print("circle-radious:\(radious)")
}

//輸出以下內(nèi)容:
//circle-radious:10.0
匹配不同枚舉值的相同關(guān)聯(lián)值
enum Shape {
    case circle(radious: Double, diameter: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, width: Double)
}

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

switch shape {
    case let .circle(x, 20.0), let .square(x, 20.0):
        print("x:\(x)")
    default:
        print("default")
}

//輸出以下內(nèi)容:
//x:10.0

上述代碼,將多個(gè)枚舉值中循签,我們想要匹配的關(guān)聯(lián)值用x代替级乐。如果枚舉值為circlesquare,且第二個(gè)關(guān)聯(lián)值為20.0县匠,即為匹配成功

通過(guò)SIL代碼风科,查看不同枚舉值的相同關(guān)聯(lián)值是如何匹配的

enum Shape {
    case circle(radious: Double, diameter: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, width: Double)
}

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

var tmpR: Double

switch shape {
    case let .circle(x, 20.0), let .square(x, 20.0):
        tmpR=x
    default:
        print("default")
}

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main

bb1、bb2乞旦、bb3

不同枚舉值里贼穆,用到匹配的變量或常量x,必須名稱相同兰粉,不能一個(gè)用x一個(gè)用y故痊,否則編譯報(bào)錯(cuò)

編譯報(bào)錯(cuò)

使用通配符_方式匹配

enum Shape {
    case circle(radious: Double, diameter: Double)
    case rectangle(width: Double, height: Double)
    case square(width: Double, width: Double)
}

var shape = Shape.circle(radious: 10.0, diameter: 30.0)

switch shape {
    case let .circle(x, _), let .square(x, _):
        print("x:\(x)")
    default:
        print("default")
}

//輸出以下內(nèi)容:
//x:10.0

上述代碼,我們不關(guān)心第二個(gè)關(guān)聯(lián)值是什么玖姑,可以使用通配符_代替愕秫。如果枚舉值為circlesquare,第二個(gè)關(guān)聯(lián)為任意值焰络,都能匹配成功

同樣使用單個(gè)case進(jìn)行匹配豫领,也可以使用通配符_

var shape = Shape.circle(radious: 10.0, diameter: 20.0)

if case let Shape.circle(x, _) = shape{
    print("x:\(x)")
}

//輸出以下內(nèi)容:
//x:10.0
枚舉嵌套
enum CombineDirect{
    enum BaseDirect{
        case up
        case down
        case left
        case 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)
}

var combind = CombineDirect.leftDown(baseDirect1: .left, baseDirect2: .down)

上述代碼,通過(guò)BaseDirect枚舉的up舔琅、downleft洲劣、right四個(gè)case备蚓,組合出CombineDirect枚舉中leftUprightUp囱稽、leftDown郊尝、rightDown四個(gè)case。這種方式下BaseDirect枚舉相當(dāng)于是私有的战惊,外界無(wú)法直接訪問(wèn)

結(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")
        }
    }
}

let kill = Skill.init(key: .up)
kill.launchSkill()

//輸出以下內(nèi)容:
//up, down
枚舉中包含屬性

enum中能包含計(jì)算屬性流昏,類型屬性,不能包含存儲(chǔ)屬性

enum Shape {

    case circle(radius: Double)
    case rectangle(width: Double, height: Double)

    var width: Double{
        get{
            return 10.0
        }
    }

    static let height = 20.0
}

enum中包含存儲(chǔ)屬性,編譯報(bào)錯(cuò)

編譯報(bào)錯(cuò)

結(jié)構(gòu)體可以包含存儲(chǔ)屬性况凉,因?yàn)榻Y(jié)構(gòu)體的大小就是存儲(chǔ)屬性的大小谚鄙。但enum大小取決于case的個(gè)數(shù),只要case個(gè)數(shù)沒(méi)有超過(guò)255刁绒,enum的大小就是1字節(jié)

  • 計(jì)算屬性本質(zhì)是get闷营、set方法,對(duì)于值類型來(lái)說(shuō)根本不用存儲(chǔ)方法
  • 類型屬性是全局變量知市,它的存儲(chǔ)也和enum沒(méi)有任何關(guān)系
枚舉中包含?法

可以在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 week: Week = .SUN
week.nextDay()

print(week)

enumnextDay方法中修改自身,需要使用mutating關(guān)鍵字修飾

枚舉的大小

rawValue枚舉值大小

enum NoMean{
    case a
}

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//輸出以下內(nèi)容:
//stride:1
//size:0

enum中只有一個(gè)case嫂丙,大小為0娘赴,步長(zhǎng)為1。當(dāng)只有一個(gè)case的枚舉跟啤,大小為0表示這個(gè)enum是沒(méi)有意義的

enum NoMean{
    case a
    case b
}

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//輸出以下內(nèi)容:
//stride:1
//size:1

enum中有兩個(gè)case诽表,大小為1,步長(zhǎng)為1

enum NoMean{
    case a
    case b
    case c
    case d
    case e
}

print("stride:\(MemoryLayout<NoMean>.stride)")
print("size:\(MemoryLayout<NoMean>.size)")

//輸出以下內(nèi)容:
//stride:1
//size:1

enum中存在更多case腥光,依然是大小為1关顷,步長(zhǎng)為1

將枚舉值ab武福、c賦值給三個(gè)常量

賦值

通過(guò)斷點(diǎn)查看匯編代碼议双,分析枚舉值ab捉片、c

匯編代碼
通過(guò)斷點(diǎn)可以看出a平痰、bc分別是0x0伍纫、0x1宗雇、0x2,對(duì)系統(tǒng)來(lái)說(shuō)就是0莹规、1赔蒲、2。所以rawValue枚舉值默認(rèn)是UInt8類型良漱,占1字節(jié)舞虱,最大可以存儲(chǔ)255。超過(guò)255個(gè)枚舉值母市,系統(tǒng)會(huì)將UInt8升級(jí)為UInt16矾兜、UInt32UInt64

通過(guò)lldb查看內(nèi)存

lldb
當(dāng)前枚舉的步?是1字節(jié)患久,也就意味著如果在內(nèi)存中連續(xù)存儲(chǔ)NoMean椅寺,需要跨越1字節(jié)的?度浑槽。1字節(jié)也就是8 位,最?可以表達(dá)的數(shù)字是255

關(guān)聯(lián)值枚舉的大小

enum Shape {
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
}

print("stride:\(MemoryLayout<Shape>.stride)")
print("size:\(MemoryLayout<Shape>.size)")

//輸出以下內(nèi)容:
//stride:24
//size:17

關(guān)聯(lián)值枚舉??返帕,取決于最?關(guān)聯(lián)值??桐玻,并加上1字節(jié)枚舉值大小。

  • circle有一個(gè)Double類型關(guān)聯(lián)值溉旋,占8字節(jié)
  • rectangle有兩個(gè)Double類型關(guān)聯(lián)值畸冲,占16字節(jié)
  • enum的大小,就是最大關(guān)聯(lián)值16字節(jié)观腊,再加枚舉值1字節(jié)邑闲,共占17字節(jié)
  • stride由于8字節(jié)對(duì)齊,所以自動(dòng)補(bǔ)齊到24字節(jié)

通過(guò)lldb查看內(nèi)存

lldb

枚舉嵌套的大小

enum CombineDirect{
    enum BaseDirect{
        case up
        case down
        case left
        case 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("stride:\(MemoryLayout<CombineDirect>.stride)")
print("size:\(MemoryLayout<CombineDirect>.size)")

//輸出以下內(nèi)容:
//stride:2
//size:2

枚舉嵌套和關(guān)聯(lián)值枚舉一樣梧油,同樣取決于關(guān)聯(lián)值大小

通過(guò)lldb查看內(nèi)存

lldb

  • BaseDirect中的up苫耸、downleft儡陨、right對(duì)應(yīng)的枚舉值分別為0褪子、12骗村、3
  • CombineDirect中的leftUp嫌褪、rightUpleftDown胚股、rightDown對(duì)應(yīng)的枚舉值分別為0笼痛、48琅拌、12缨伊。這里并沒(méi)有規(guī)律可尋,如果加入更多case进宝,也會(huì)變成0刻坊、12党晋、3...向后遞增谭胚,通過(guò)源碼分析目前還未找到相關(guān)定義
  • 圖中輸出的02left的枚舉值,81要拆開(kāi)來(lái)看未玻,8leftDown的枚舉值灾而,1down的枚舉值
  • enum大小占2字節(jié),因?yàn)?code>leftDown的枚舉值和down的枚舉值存儲(chǔ)在同一字節(jié)內(nèi)深胳,屬于系統(tǒng)優(yōu)化

結(jié)構(gòu)體中嵌套枚舉的大小

struct Skill{
    enum KeyType{
        case up
        case down
        case left
        case right
    }

    let key: KeyType
}

print("stride:\(MemoryLayout<Skill>.stride)")
print("size:\(MemoryLayout<Skill>.size)")

//輸出以下內(nèi)容:
//stride:1
//size:1

結(jié)構(gòu)體中有一個(gè)KeyType枚舉類型的成員變量key,所以結(jié)構(gòu)體大小為1铜犬,步長(zhǎng)為1

indirect關(guān)鍵字

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

創(chuàng)建鏈表結(jié)構(gòu)enum轻庆,對(duì)應(yīng)當(dāng)前遞歸枚舉來(lái)說(shuō),不添加indirect關(guān)鍵字敛劝,編譯報(bào)錯(cuò)

編譯報(bào)錯(cuò)

因?yàn)?code>enum是值類型余爆,它會(huì)在編譯時(shí)期確定大小。但對(duì)于接收泛型Tenum夸盟,編譯時(shí)期無(wú)法確定enum大小蛾方,系統(tǒng)無(wú)法分配空間

enum List<T>{
    case end
    indirect case node(T, next: List<T>)
}

上述代碼,在case node前面添加indirect關(guān)鍵字上陕,可以編譯通過(guò)

indirect enum List<T>{
    case end
    case node(T, next: List<T>)
}

另一種方式桩砰,可以在enum List<T>前面添加indirect關(guān)鍵字,同樣可以編譯通過(guò)

indirect enum List<T>{
    case end
    case node(T, next: List<T>)
}

print("List<Int> stride:\(MemoryLayout<List<Int>>.stride)")
print("List<Int> size:\(MemoryLayout<List<Int>>.size)")

print("List<String> stride:\(MemoryLayout<List<String>>.stride)")
print("List<String> size:\(MemoryLayout<List<String>>.size)")

上述代碼释簿,分別將IntString傳入enum亚隅,打印出來(lái)的大小都是8字節(jié),下面來(lái)分析一下原因

通過(guò)lldb查看內(nèi)存
打印case end庶溶,存儲(chǔ)的是end枚舉值

end

打印case node煮纵,存儲(chǔ)的是堆區(qū)地址

node

indirect關(guān)鍵字本質(zhì)就是通知編譯器,當(dāng)前enum是遞歸枚舉偏螺,無(wú)法確定大小行疏,需要在堆區(qū)空間分配內(nèi)存,并存儲(chǔ)enum

通過(guò)SIL代碼套像,查看indirect關(guān)鍵字酿联,如何分配堆區(qū)內(nèi)存空間

indirect enum List<T>{
    case end
    case node(T, next: List<T>)
}

var node = List<Int>.node(10, next: List<Int>.end)

將上述代碼生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

main

通過(guò)斷點(diǎn)查看匯編代碼,確實(shí)執(zhí)行了swift_allocObject

lldb

枚舉-Swift和OC混編
OC調(diào)用Swift的枚舉

OC只能調(diào)用SwiftInt類型枚舉

@objc enum NoMean: Int{
    case a
    case b
    case c
    case d
}

通過(guò)@objc聲明后凉夯,橋接文件中自動(dòng)生成SWIFT_ENUM

typedef SWIFT_ENUM(NSInteger, NoMean, closed) {
  NoMeanA = 0,
  NoMeanB = 1,
  NoMeanC = 2,
  NoMeanD = 3,
};

OCLGTest.m文件中可以直接調(diào)用

@implementation LGTest

- (void)test{
    NoMean a = NoMeanA;
}

@end

如果enum不聲明類型货葬,同時(shí)使用@objc修飾,編譯報(bào)錯(cuò)

不聲明類型

如果enum聲明String類型劲够,同時(shí)使用@objc修飾震桶,編譯報(bào)錯(cuò)

聲明String類型

Swift調(diào)用OC的枚舉

OCLGTest.h中,使用typedef NS_ENUM聲明枚舉

typedef NS_ENUM(NSInteger, CEnum) {
    CEnumInvalid = 0,
    CEnumA = 1,
    CEnumB,
    CEnumC
};

在橋接文件中征绎,自動(dòng)生成enum CEnum

public enum CEnum : Int {
    case invalid = 0
    case A = 1
    case B = 2
    case C = 3
}

OCLGTest.h中蹲姐,使用typedef enum聲明枚舉

typedef enum{
    Num1,
    Num2
} OCNum;

在橋接文件中,自動(dòng)生成struct OCNum人柿,變成了結(jié)構(gòu)體柴墩,并遵循了 EquatableRawRepresentable協(xié)議

public struct OCNum : Equatable, RawRepresentable {
    public init(_ rawValue: UInt32)
    public init(rawValue: UInt32)
    public var rawValue: UInt32
}

Swiftmain.swift文件中可以直接調(diào)用

let a: CEnum = .A
let b: OCNum = OCNum.init(rawValue: 1)

print("CEnum:\(a.rawValue)")
print("OCNum:\(b.rawValue)")

//輸出以下內(nèi)容:
//CEnum:1
//OCNum:1
內(nèi)存對(duì)齊 & 字節(jié)對(duì)齊
  • 內(nèi)存對(duì)齊:iOS采用8字節(jié)對(duì)齊方式,只會(huì)在對(duì)象初始化分配內(nèi)存時(shí)出現(xiàn)凫岖。例如malloc绊谭、calloc
  • 字節(jié)對(duì)齊:第一個(gè)數(shù)據(jù)成員放在offset0的位置新蟆,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員大小或該成員的子成員大小(只要該成員有子成員礁扮,比如數(shù)組、結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始。比如Int8字節(jié),則要從8的整數(shù)倍地址開(kāi)始存儲(chǔ)
enum Shape {
    case circle(radius: Double)
    case rectangle(width: Int8, height: Int, w: Int16, h: Int32)
}

print("stride:\(MemoryLayout<Shape>.stride)")
print("size:\(MemoryLayout<Shape>.size)")

//輸出以下內(nèi)容:
//stride:24
//size:24
  • widthInt8類型,占1字節(jié)
  • heightInt類型胀茵,占8字節(jié)
  • wInt16類型,占2字節(jié)
  • hInt32類型挟阻,占4字節(jié)
  • width1字節(jié)琼娘,但第二成員height8字節(jié)。按字節(jié)對(duì)齊規(guī)則附鸽,height起始位置必須是自身的整數(shù)倍脱拼,所以width要補(bǔ)齊到8字節(jié)。而w2字節(jié)拒炎,但h4字節(jié)挪拟,所以同理w要補(bǔ)齊到4字節(jié)。最終size大谢髂恪:8 + 8 + 4 + 4 = 24字節(jié)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玉组,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丁侄,更是在濱河造成了極大的恐慌惯雳,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸿摇,死亡現(xiàn)場(chǎng)離奇詭異石景,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拙吉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門潮孽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人筷黔,你說(shuō)我怎么就攤上這事往史。” “怎么了佛舱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵椎例,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我请祖,道長(zhǎng)订歪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任肆捕,我火速辦了婚禮刷晋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己眼虱,他們只是感情好或舞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蒙幻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胆筒。 梳的紋絲不亂的頭發(fā)上邮破,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音仆救,去河邊找鬼抒和。 笑死,一個(gè)胖子當(dāng)著我的面吹牛彤蔽,可吹牛的內(nèi)容都是我干的摧莽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼顿痪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼镊辕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚁袭,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤征懈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后揩悄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體卖哎,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年删性,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亏娜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹬挺,死狀恐怖维贺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汗侵,我是刑警寧澤幸缕,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站晰韵,受9級(jí)特大地震影響发乔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雪猪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一栏尚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧只恨,春花似錦译仗、人聲如沸抬虽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)阐污。三九已至,卻和暖如春咱圆,著一層夾襖步出監(jiān)牢的瞬間笛辟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工序苏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留手幢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓忱详,卻偏偏與公主長(zhǎng)得像围来,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匈睁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • Swift 進(jìn)階之路 文章匯總[http://www.reibang.com/p/5fbedf309237] 本...
    Style_月月閱讀 1,985評(píng)論 1 3
  • swift進(jìn)階 學(xué)習(xí)大綱[http://www.reibang.com/p/0fc67b373540] 本節(jié)监透,分...
    markhetao閱讀 1,610評(píng)論 0 5
  • C語(yǔ)言的枚舉 C語(yǔ)言的枚舉寫法 我們通過(guò)枚舉表示一周的七天 c語(yǔ)言中,枚舉的第一個(gè)成員默認(rèn)是為0航唆,后面的枚舉值一次...
    浪的出名閱讀 382評(píng)論 0 1
  • C enum C語(yǔ)言枚舉格式 比如要表示星期幾的枚舉: 第一個(gè)枚舉成員的默認(rèn)值為0才漆,依次加1。要改變枚舉值直接賦值...
    HotPotCat閱讀 629評(píng)論 1 3
  • C語(yǔ)言枚舉 一周七天可以寫成 第?個(gè)枚舉成員的默認(rèn)值為整型的 0佛点,后?的枚舉值依次類推醇滥,如果我們想更改,只需要這樣...
    Mjs閱讀 228評(píng)論 0 1