Swift枚举

C语言的枚举

  • C语言的枚举写法
enum 枚举名 { 
    枚举值1,
    枚举值2,
    ......
 };
  • 我们通过枚举表示一周的七天
enum Week {
    MON,TUS,WED,THU,FRI,SAT,SUN
};
  • c语言中,枚举的第一个成员默认是为0,后面的枚举值一次类推,也可以直接指定对应的值,它后面的枚举值依然是一次递增
enum Week {
    MON=3,TUS,WED,THU=9,FRI,SAT,SUN
};
// 这个枚举对应的值就是3,4,5,9,10,11,12

Swift的枚举

Swift枚举的使用

  • 同样实现这个枚举,其写法如下
enum Week {
    case MON
    case TUS
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
// 也可以直接一个case,用逗号隔开
enum Week {
    case MON,TUS,WED,THU,FRI,SAT,SUN
}
  • 这样表达的是一个整型的枚举,和c语言的是一致的,Swift中也可以用枚举表达String
enum Week : String {
    case MON = "MON"
    case TUS = "TUS"
    case WED = "WED"
    case THU = "THU"
    case FRI = "FRI"
    case SAT = "SAT"
    case SUN = "SUN"
}
  • =右边就是Swift中的RawValue,如果我们没有写后面的字符串,会默认的隐式RawValue分配,也可以对枚举的成员单独指定RawValue
  • 查看sil文件,可以看到自动生成了一个init?(rawValue:)方法,和一个get方法
enum Week : String {
  case MON
  case TUS
  case WED
  case THU
  case FRI
  case SAT
  case SUN
  typealias RawValue = String
  init?(rawValue: String)
  var rawValue: String { get }
}
  • 我们来看下init?(rawValue:)函数是什么时候调用的,通过符号断点来观察
    image
  • 运行发现:Week.MON.rawValue并不会触发init函数,只有调用Week(rawValue: "MON")才会进到符号断点,再通过打印下面的代码
print(Week(rawValue: "MON"))
print(Week(rawValue: "hello"))

// 打印结果  
Optional(SwiftEnumerate.Week.MON)
nil
  • 查看sil文件中init方法的实现,首先我们会将枚举值全部放到一个数组里面,然后通过_findStringSwitchCase方法匹配传入的字符串,如果匹配到了就返回一个index,没有匹配到就返回一个-1,再循环遍历所有的枚举值,匹配到了就取出对应的枚举值,然后包装成一个可选项的值返回,如果都没有匹配到返回的就是nil,所以返回值是一个可选项。
// Week.init(rawValue:)
sil hidden @$s4main4WeekO8rawValueACSgSS_tcfC : $@convention(method) (@owned String, @thin Week.Type) -> Optional<Week> {
// %0 "rawValue"                                  // users: %164, %158, %79, %3
// %1 "$metatype"
bb0(%0 : $String, %1 : $@thin Week.Type):
  %2 = alloc_stack $Week, var, name "self"        // users: %162, %154, %143, %132, %121, %110, %99, %88, %165, %159
  debug_value %0 : $String, let, name "rawValue", argno 1 // id: %3
  %4 = integer_literal $Builtin.Word, 7           // user: %6
  // function_ref _allocateUninitializedArray<A>(_:) 创建一个元组,里面有枚举值数组,和指针
  %5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
  %6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
  %7 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 0 // users: %80, %79 
  %8 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 1 // user: %9
  %9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*StaticString // users: %17, %69, %59, %49, %39, %29, %19
  %10 = string_literal utf8 "MON"                 // user: %12 创建一个字符串
  %11 = integer_literal $Builtin.Word, 3          // user: %16 它的长度是3,我们在内存中分配的大小
  %12 = builtin "ptrtoint_Word"(%10 : $Builtin.RawPointer) : $Builtin.Word // user: %16
  br bb1                                          // id: %13

bb1:                                              // Preds: bb0
  %14 = integer_literal $Builtin.Int8, 2          // user: %16
  br bb2                                          // id: %15

bb2:                                              // Preds: bb1
  %16 = struct $StaticString (%12 : $Builtin.Word, %11 : $Builtin.Word, %14 : $Builtin.Int8) // user: %17
  store %16 to %9 : $*StaticString                // id: %17
  %18 = integer_literal $Builtin.Word, 1          // user: %19
  %19 = index_addr %9 : $*StaticString, %18 : $Builtin.Word // user: %27 获取当前数组中的值的地址,返回相对于首地址的第(%18)个元素的地址
  %20 = string_literal utf8 "TUS"                 // user: %22
  %21 = integer_literal $Builtin.Word, 3          // user: %26
  %22 = builtin "ptrtoint_Word"(%20 : $Builtin.RawPointer) : $Builtin.Word // user: %26
  br bb3                                          // id: %23
  ......
bb14:                                             // Preds: bb13
  %76 = struct $StaticString (%72 : $Builtin.Word, %71 : $Builtin.Word, %74 : $Builtin.Int8) // user: %77
  store %76 to %69 : $*StaticString               // id: %77
  // function_ref _findStringSwitchCase(cases:string:)
  %78 = function_ref @$ss21_findStringSwitchCase5cases6stringSiSays06StaticB0VG_SStF : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // user: %79 遍历之前创建的数组匹配就返回对应的index
  %79 = apply %78(%7, %0) : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // users: %149, %138, %127, %116, %105, %94, %83, %147, %136, %125, %114, %103, %92, %81
  release_value %7 : $Array<StaticString>         // id: %80
  debug_value %79 : $Int, let, name "$match"      // id: %81
  %82 = integer_literal $Builtin.Int64, 0         // user: %84
  %83 = struct_extract %79 : $Int, #Int._value    // user: %84
  %84 = builtin "cmp_eq_Int64"(%82 : $Builtin.Int64, %83 : $Builtin.Int64) : $Builtin.Int1 // user: %85  循环比较当前的返回值和index,根据结果往下跳转不同的分支
  cond_br %84, bb15, bb16                         // id: %85
  
bb15:                                             // Preds: bb14
  %86 = metatype $@thin Week.Type
  %87 = enum $Week, #Week.MON!enumelt             // user: %89
  %88 = begin_access [modify] [static] %2 : $*Week // users: %89, %90
  store %87 to %88 : $*Week                       // id: %89
  end_access %88 : $*Week                         // id: %90
  br bb29                                         // id: %91
......
// 遍历完全部枚举值都没有找到,就会返回一个none
bb28:                                             // Preds: bb26
  release_value %0 : $String                      // id: %158
  dealloc_stack %2 : $*Week                       // id: %159
  %160 = enum $Optional<Week>, #Optional.none!enumelt // user: %161
  br bb30(%160 : $Optional<Week>)                 // id: %161
// 匹配成功就创建一个可选项返回
bb29:                                             // Preds: bb27 bb25 bb23 bb21 bb19 bb17 bb15
  %162 = load %2 : $*Week                         // user: %163
  %163 = enum $Optional<Week>, #Optional.some!enumelt, %162 : $Week // user: %166
  release_value %0 : $String                      // id: %164
  dealloc_stack %2 : $*Week                       // id: %165
  br bb30(%163 : $Optional<Week>)                 // id: %166

// %167                                           // user: %168
bb30(%167 : $Optional<Week>):                     // Preds: bb29 bb28
  return %167 : $Optional<Week>                   // id: %168
} // end sil function '$s4main4WeekO8rawValueACSgSS_tcfC'
  • Swift源码中_findStringSwitchCase的实现
/// The compiler intrinsic which is called to lookup a string in a table
/// of static string case values.
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
func _findStringSwitchCase(
  cases: [StaticString],
  string: String) -> Int {

  for (idx, s) in cases.enumerated() {
    if String(_builtinStringLiteral: s.utf8Start._rawValue,
              utf8CodeUnitCount: s._utf8CodeUnitCount,
              isASCII: s.isASCII._value) == string {
      return idx
    }
  }
  return -1
}
  • 再看下它是如何获取rawValue
// main 这里调用Week.rawValue.getter,返回一个字符串
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1aSSvp                     // id: %2
  %3 = global_addr @$s4main1aSSvp : $*String      // user: %8
  %4 = metatype $@thin Week.Type
  %5 = enum $Week, #Week.MON!enumelt              // user: %7 创建一个枚举值 MON
  // function_ref Week.rawValue.getter
  %6 = function_ref @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String // user: %7
  %7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
  store %7 to %3 : $*String                       // id: %8
  %9 = integer_literal $Builtin.Int32, 0          // user: %10
  %10 = struct $Int32 (%9 : $Builtin.Int32)       // user: %11
  return %10 : $Int32                             // id: %11
} // end sil function 'main'

// Week.rawValue.getter
sil hidden @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Week):
  debug_value %0 : $Week, let, name "self", argno 1 // id: %1
  switch_enum %0 : $Week, case #Week.MON!enumelt: bb1, case #Week.TUS!enumelt: bb2, case #Week.WED!enumelt: bb3, case #Week.THU!enumelt: bb4, case #Week.FRI!enumelt: bb5, case #Week.SAT!enumelt: bb6, case #Week.SUN!enumelt: bb7 // id: %2
// 匹配枚举值,构建字符串返回
bb1:                                              // Preds: bb0
  %3 = string_literal utf8 "MON"                  // user: %8
  %4 = integer_literal $Builtin.Word, 3           // 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 bb8(%8 : $String)                            // id: %9
  ...... //省略掉其他case的判断,只判断为MON的时候
// %52                                            // user: %53
bb8(%52 : $String):                               // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
  return %52 : $String                            // id: %53
} // end sil function '$s4main4WeekO8rawValueSSvg'
  • 这种情况下枚举的值都是字符串常量,存储的位置应该是在mach-o文件的__TEXT,__cstring,可以看到,当前我们指定enumRawValueString之后,系统自动创建了一块连续的内存空间来默认存放当前case对应的字符串。
    image

这里需要注意的一点就是枚举值和RawValue是两个不同的东⻄,我们没有办法把一个枚举值分配一个String的变量,即使这个enumString类型的

关联值

  • Swift中,我们可以通过枚举值表达一个复杂的信息,就是关联值,通过枚举来表达一个形状,有圆形,⻓方形,圆形有半径,⻓方形有宽,高,这个时候关联值就显得非常有用了
enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
}
  • 我们使用了关联值之后,就没有RawValue,因为我们每个case都有一组值来表达,所以不需要使用RawValue,这里我们使用的radiouswidthheight是我们自己取的标签,可以省略的
  • 创建一个关联值的枚举值
enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
    
}

var circle = Shape.circle(radious: 10.0)
var rectangle = Shape.rectangle(width: 15.0, height: 18.0)

枚举的其他用法

模式匹配

  • 使用switch匹配枚举值时,需要遍历所有可能的情况,不然编译器会报错,如果不想匹配所有的case,可以使用defalut关键字代表默认的情况。
    image
  • 关联值的匹配
enum Shape {
    case circle(radious:Double)
    case rectangle(width:Double,height:Double)
    
}

var circle = Shape.circle(radious: 10.0)
var rectangle = Shape.rectangle(width: 15.0, height: 18.0)

switch circle {
    case let .circle(radious): print("circle radious\(radious)")
    case let .rectangle(width, height) : print("rectangle width:\(width),height:\(height)")
}
switch rectangle {
    case .circle(let radious): print("circle radious\(radious)")
    case .rectangle(let width,let height) : print("rectangle width:\(width),height:\(height)")
}
  • 匹配单个关联值
if case let Shape.circle(radious) = circle {
    print(radious)
}
  • 不同的case的相同的关联值
enum Shape{ 
    case circle(radious: Double, diameter: Double) 
    case rectangle(width: Double, height: Double) 
    case square(width: Double, width: Double) 
}
let shape = Shape.circle(radious: 10.0, diameter: 20.0) 
switch shape {
    case let .circle(x, 20.0), let .square(x, 20.0): 
        print(x) 
    default: 
        break 
} 
  • 使用通配符
switch shape {
    case let .circle(_, x), let .square(x, _):
        print(x) 
    default:
        break
}

switch shape {
    case let .circle(x, y), let .rectangle(y, x):
        print("x=\(x),y=\(y)") 
    default:
        break
}

枚举的嵌套

  • 枚举中嵌套枚举
enum CombineDirect{
    enum BaseDirect{
        case up
        case down
        case left
        case right
    }

    case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
    case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
    case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
    case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}

结构体中的嵌套

  • 结构体中嵌套枚举
struct Skill {
    enum KeyType {
        case up
        case down
        case left
        case right
    }
    
    let key : KeyType
}

枚举中包含属性和方法

  • 枚举中能够包含计算属性,类型属性,不能包含存储属性


    image
  • 枚举中定义实例方法、static方法
enum Week : Int {
    case MON
    case TUS
    case WED
    case THU
    case FRI
    case SAT
    case SUN
    
    mutating func nextDay() {
        if self == .SUN {
            self = Week(rawValue: 1)!
        }else{
            self = Week(rawValue: 1+self.rawValue)!
        }
    }
    static func doSomething() {
        print("111\(self.init(rawValue: 1))")
    }
    
}

枚举的大小

  • 一个case时候的大小
enum noMean {
    case a
}

print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)

// 打印结果为:0 1
  • 普通的case没有关联值的时候,枚举的大小为一个字节,一个字节所能表示的最大值为255,如果我们的case大于255的时候大小就会由Int8->Int16,如果不够就会继续扩大2倍
enum noMean {
    case a
    case b
}

print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)

// 打印结果为:1 1

// 修改rawValue的类型 打印结果不变
enum noMean : String {
    case a
    case b
}
// 打印结果为:1 1
  • 有关联值时候的枚举的大小取决于最大的case内存大小,但是大家看每次应该是16, 24, 32 这样的偶数,但是这里每次都是多加1,这里是不是就是我们当前case的值啊,占用 1 字节,只有一个case的时候,默认size为0。
enum Shape{
    case circle(radious: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印结果为 8,8

enum Shape{
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印结果为17,24
  • 打印内存结构


    image
需要注意的一点是,由于字节对齐的原因,有时候`case`的1字节会被编译器优化加到前面的位置上,它的大小不会再+1
  • 通过一段代码来验证下刚刚的结论


    image
  • 修改circleradious的类型为Int
    image
  • 总结
    • 1,RawValue的枚举大小默认是一字节(UInt8),也就意味着当前最大能存储的大小是 255。如果超过这个大小,当前枚举的大小就会从UInt8->UInt16依次类推
    • 2,关联值的枚举大小与最大的case的内存大小相关。

indirect关键字

  • 我们想通过枚举实现一个链表,就需要使用indirect关键字
    image
  • 通过编译器的修正后的写法如下
indirect enum List<T> {
    case end
    case node(T, next : List<T>)
}
或
enum List<T> {
    case end
    indirect case node(T, next : List<T>)
}
  • 枚举是值类型,也就意味着它的大小在编译期就确定了,那么这个过程中对于我们当前这个List的大小就不能确定,从系统的⻆度,我不知道当前要给这个枚举分配多大的内存空间。所以怎么办?官方文档上有如下解释

You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.

  • 打印下它的大小


    image
  • 打印下实例对象的内存结构


    image
  • 查看sil文件,alloc_box的本质就是调用swift_allocObjet,所以indirect关键字其实就是通知编译器,我当前的枚举是递归的,自然而然大小也就不确定,所以赶紧给我分配一块堆区的内存空间存放。
    image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容