美文网首页
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