美文网首页
Swift枚举

Swift枚举

作者: 吕建雄 | 来源:发表于2021-08-09 16:29 被阅读0次

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模式

开启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方法底层分析

init方法

从上图可知:(593行开始)

调用方法创建一个数组,并返回一个元祖(tuple)类型

元祖第一个存放元素的值(595行)

元祖第二个存放当前的指针(596行)

创建字符串的引用 (598行)

当前字符串的长度为5,即在内存中分配的大小 (599行)

init方法

调用了一个方法:_findStringSwitchCase(cases:string:)去匹配

取出当前index (660行)

比较当前的返回值于index。(661行)

根据条件进行分支调整,如果成功跳转到bb9,如果不成功跳转到bb10 (662行)

bb9 可知 成功则返回当前的enum (见下图bb17)

bb10可知,不成功则继续匹配(见下图:bb11,bb12)

init方法 init方法

如果全部没有匹配,那么构建一个.none类型的Optional 表示nil (见bb16,第713行)

如果匹配成功,则构建一个.some类型的Optional,表示有值(见bb17,第718行)

所以当enum匹配不上时,会返回nil的原因

总结:

使用rawValue的本质是调用get方法(get方法中的String在编译时期就已经存储好了,即存放在Mach-O文件的__Text.cstring中,且是连续的内存空间)

rawValue的get方法中的分支构建的字符串,主要是从Mach-O文件对应地址取出的字符串,然后在返回

相关文章

网友评论

      本文标题:Swift枚举

      本文链接:https://www.haomeiwen.com/subject/srmzvltx.html