Swift中,枚舉的創(chuàng)建方式如下;
/*寫法1*/
enum LTSeasonOne{
? ? case FIRST
? ? case SECOND
? ? case THIRD
? ? case FORTH
}
/*寫法2*/
enum LTSeasonTwo{
? ? case FIRST,SECOND,THIRD,FORTH
}
如果沒有指定枚舉值的類型,那么enum默認枚舉值是整型的
創(chuàng)建一個枚舉值是String類型的enum(通過指定enum的枚舉值類型來創(chuàng)建)
/*寫法3*/
enum LTSeasonThree: String{
? ? case FIRST = "FIRST",SECOND = "SECOND",THIRD = "THIRD",FORTH = "FORTH"
}
通過查看SIL文件,來探究下具體底層實現(xiàn)邏輯
生成SIL文件,命令:swiftc -emit-sil main.swift > main.sil 其中main.swift是swift文件名
enum LTSeasonTwo {
?? case FIRST,SECOND,THIRD,FORTH
?? @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: LTSeasonTwo, _ b: LTSeasonTwo) -> Bool
?? func hash(into hasher: inout Hasher)
?? var hashValue: Int { get }
?}
通過SIL文件可知,默認類型的enum,底層實現(xiàn)邏輯
1、底層默認實現(xiàn)Equatable協(xié)議,并實現(xiàn)__derived_enum_equals方法
2、增加屬性hashValue用于獲取枚舉值的原始值
enum LTSeasonThree : String {
?? case FIRST, SECOND, THIRD, FORTH
?? init?(rawValue: String)
?? typealias RawValue = String
?? var rawValue: String { get }
?}
通過SIL文件可知,指定類型的enum,SIL文件中的enum實現(xiàn)邏輯
1、默認增加了一個可選類型的init方法
2、給枚舉類型,通過typealias取了一個別名:RawValue
3、增加一個屬性rawValue,用于獲取枚舉值的原始值
通過SIL文件來,分析rawValue的get方法:
// LTSeasonThree.rawValue.getter
?sil hidden @$s4main13LTSeasonThreeO8rawValueSSvg : $@convention(method) (LTSeasonThree) -> @owned String {
?// %0 "self"? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // users: %2, %1
?bb0(%0 : $LTSeasonThree):
?? debug_value %0 : $LTSeasonThree, let, name "self", argno 1 // id: %1
?? switch_enum %0 : $LTSeasonThree, case #LTSeasonThree.FIRST!enumelt: bb1, case #LTSeasonThree.SECOND!enumelt: bb2, case #LTSeasonThree.THIRD!enumelt: bb3, case #LTSeasonThree.FORTH!enumelt: bb4 // id: %2
?bb1:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Preds: bb0
?? %3 = string_literal utf8 "FIRST"? ? ? ? ? ? ? ? // user: %8
?? %4 = integer_literal $Builtin.Word, 5? ? ? ? ? // 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 @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@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 bb5(%8 : $String)? ? ? ? ? ? ? ? ? ? ? ? ? ? // id: %9
?bb2:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Preds: bb0
?? %10 = string_literal utf8 "SECOND"? ? ? ? ? ? ? // user: %15
?? %11 = integer_literal $Builtin.Word, 6? ? ? ? ? // 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 @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@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 bb5(%15 : $String)? ? ? ? ? ? ? ? ? ? ? ? ? // id: %16
?bb3:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Preds: bb0
?? %17 = string_literal utf8 "THIRD"? ? ? ? ? ? ? // user: %22
?? %18 = integer_literal $Builtin.Word, 5? ? ? ? ? // 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 @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@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 bb5(%22 : $String)? ? ? ? ? ? ? ? ? ? ? ? ? // id: %23
?bb4:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Preds: bb0
?? %24 = string_literal utf8 "FORTH"? ? ? ? ? ? ? // user: %29
?? %25 = integer_literal $Builtin.Word, 5? ? ? ? ? // 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 @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@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 bb5(%29 : $String)? ? ? ? ? ? ? ? ? ? ? ? ? // id: %30
?// %31? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // user: %32
?bb5(%31 : $String):? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Preds: bb4 bb3 bb2 bb1
?? return %31 : $String? ? ? ? ? ? ? ? ? ? ? ? ? ? // id: %32
?} // end sil function '$s4main13LTSeasonThreeO8rawValueSSvg'
通過上述SIL文件可知,rawVa lue的get方法,主要有以下幾步:
1、接收一個枚舉值,用于匹配對應的分支?bb0(%0 : $LTSeasonThree):
2、在對應分支創(chuàng)建對應的String ?switch_enum %0 : $LTSeasonThree, case #LTSeasonThree.FIRST!enumelt: bb1, case #LTSeasonThree.SECOND!enumelt: bb2, case #LTSeasonThree.THIRD!enumelt: bb3, case #LTSeasonThree.FORTH!enumelt: bb4 // id: %2
3、返回對應的String ?return %31 : $String? ? ? ? ? ? ? ? ? ? ? ? ? ? // id: %32
了解了枚舉的底層原理之后,繼續(xù)探索枚舉的init調(diào)用時機
1、定義一個符號斷點

2、開啟Debug模式

使用如下代碼調(diào)用:
print(LTSeasonThree.FIRST)
print(LTSeasonThree.FIRST.rawValue)
運行后發(fā)現(xiàn)init方法都不會進入
通過如下方式調(diào)用:
print(LTSeasonThree.init(rawValue: "FIRST")!)
print(LTSeasonThree(rawValue: "FIRST")!)
發(fā)現(xiàn)init方法可以進入
得出如下結(jié)論:enum中init方法的調(diào)用是通過 .init(rawValue:) 或 (rawValue)觸發(fā)的
init方法底層分析

從上圖可知:(593行開始)
調(diào)用方法創(chuàng)建一個數(shù)組,并返回一個元祖(tuple)類型
元祖第一個存放元素的值(595行)
元祖第二個存放當前的指針(596行)
創(chuàng)建字符串的引用 (598行)
當前字符串的長度為5,即在內(nèi)存中分配的大小 (599行)

調(diào)用了一個方法:_findStringSwitchCase(cases:string:)去匹配
取出當前index (660行)
比較當前的返回值于index。(661行)
根據(jù)條件進行分支調(diào)整,如果成功跳轉(zhuǎn)到bb9,如果不成功跳轉(zhuǎn)到bb10 (662行)
bb9 可知 成功則返回當前的enum (見下圖bb17)
bb10可知,不成功則繼續(xù)匹配(見下圖:bb11,bb12)


如果全部沒有匹配,那么構(gòu)建一個.none類型的Optional 表示nil (見bb16,第713行)
如果匹配成功,則構(gòu)建一個.some類型的Optional,表示有值(見bb17,第718行)
所以當enum匹配不上時,會返回nil的原因
總結(jié):
使用rawValue的本質(zhì)是調(diào)用get方法(get方法中的String在編譯時期就已經(jīng)存儲好了,即存放在Mach-O文件的__Text.cstring中,且是連續(xù)的內(nèi)存空間)
rawValue的get方法中的分支構(gòu)建的字符串,主要是從Mach-O文件對應地址取出的字符串,然后在返回