Swift中,枚举的创建方式如下;
/*写法1*/
enum LTSeasonOne{
case FIRST
case SECOND
case THIRD
case FORTH
}
/*写法2*/
enum LTSeasonTwo{
case FIRST,SECOND,THIRD,FORTH
}
如果没有指定枚举值的类型,那么enum默认枚举值是整型的
创建一个枚举值是String类型的enum(通过指定enum的枚举值类型来创建)
/*写法3*/
enum LTSeasonThree: String{
case FIRST = "FIRST",SECOND = "SECOND",THIRD = "THIRD",FORTH = "FORTH"
}
通过查看SIL文件,来探究下具体底层实现逻辑
生成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,底层实现逻辑
1、底层默认实现Equatable协议,并实现__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实现逻辑
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、在对应分支创建对应的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
了解了枚举的底层原理之后,继续探索枚举的init调用时机
1、定义一个符号断点
2、开启Debug模式
使用如下代码调用:
print(LTSeasonThree.FIRST)
print(LTSeasonThree.FIRST.rawValue)
运行后发现init方法都不会进入
通过如下方式调用:
print(LTSeasonThree.init(rawValue: "FIRST")!)
print(LTSeasonThree(rawValue: "FIRST")!)
发现init方法可以进入
得出如下结论:enum中init方法的调用是通过 .init(rawValue:) 或 (rawValue)触发的
init方法底层分析
从上图可知:(593行开始)
调用方法创建一个数组,并返回一个元祖(tuple)类型
元祖第一个存放元素的值(595行)
元祖第二个存放当前的指针(596行)
创建字符串的引用 (598行)
当前字符串的长度为5,即在内存中分配的大小 (599行)
调用了一个方法:_findStringSwitchCase(cases:string:)去匹配
取出当前index (660行)
比较当前的返回值于index。(661行)
根据条件进行分支调整,如果成功跳转到bb9,如果不成功跳转到bb10 (662行)
bb9 可知 成功则返回当前的enum (见下图bb17)
bb10可知,不成功则继续匹配(见下图:bb11,bb12)
如果全部没有匹配,那么构建一个.none类型的Optional 表示nil (见bb16,第713行)
如果匹配成功,则构建一个.some类型的Optional,表示有值(见bb17,第718行)
所以当enum匹配不上时,会返回nil的原因
总结:
使用rawValue的本质是调用get方法(get方法中的String在编译时期就已经存储好了,即存放在Mach-O文件的__Text.cstring中,且是连续的内存空间)
rawValue的get方法中的分支构建的字符串,主要是从Mach-O文件对应地址取出的字符串,然后在返回