Swift枚举

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 枚举(enum)详解 [TOC] 本文将介绍Swift中枚举的一些用法和其底层原理的一些探索,以及探索...
    just东东阅读 16,727评论 6 22
  • 前言 本篇文章将讲述Swift中很常用的也很重要的一个知识点 👉 Enum枚举。首先会介绍与OC中枚举的差别,接着...
    深圳_你要的昵称阅读 7,282评论 0 5
  • C语言枚举 一周七天可以写成 第⼀个枚举成员的默认值为整型的 0,后⾯的枚举值依次类推,如果我们想更改,只需要这样...
    Mjs阅读 2,372评论 0 1
  • C语言的枚举 C语言的枚举写法 我们通过枚举表示一周的七天 c语言中,枚举的第一个成员默认是为0,后面的枚举值一次...
    浪的出名阅读 3,142评论 0 1
  • 1. 枚举, 使用enum来创建枚举, 类似于类的命名类型, 枚举类型赋值可以是字符串/字符/整形/浮点型, 枚举...
    chrisdev阅读 1,287评论 0 0