Swift10 - 枚举(enum)探索

599 阅读5分钟

Swift 进阶之路 文章汇总

前言:

上篇文章主要介绍了Swift枚举基础用法,用SIL文件的方式分析了基础枚举的编译结构和调用流程,这篇文章接着上篇文章的内容,继续以SIL的方式分析其他枚举类型的组织结构及枚举的内存大小

枚举的关联值分析

上篇文章中提到枚举的关联值是这样定义的:

//注:当使用了关联值后,就没有RawValue了,主要是因为case可以用一组值来表示,而rawValue是单个的值
enum Shape{
    //case枚举值后括号内的就是关联值,例如 radius
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
}

关联值的SIL文件分析

生成sil文件,查看关联枚举的构成

关联枚举Switch匹配分析

关联枚举的匹配应用:

enum Shape{
    //case枚举值后括号内的就是关联值,例如 radius
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
    case square(width: Double, width: Double) 

}
let shape = Shape.circle(radius: 10.0)
switch shape{
case let .circle(radious):
    print("Circle radious:\(radious)")
case let .rectangle(width, height):
    print("rectangle width:\(width),height\(height)")
}

生成sil文件,查看构成

查看case分支

其他关联枚举的使用:

  • 通过if case匹配单个case,如下所示

    if case let Shape.circle(radius) = circle { print("circle radius: (radius)") }

  • 如果我们只关心不同case的相同关联值(即关心不同case的某一个值),需要使用同一个参数,例如案例中的x,如果分别使用x、y, 编译器会报错

    switch shape{ case let .circle(x), let .rectangle(20, x): print(x) default: break }

  • 使用通配符_(表示匹配一切)的方式

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

    switch shape{

    case let .rectangle(x, ), let .square(, x): print("x = (x)") default: break }

注意⚠️: OC和Swift混编只能调用swift中Int类型的枚举

indirect关键字

递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上indirect来表示该成员可递归, 而且被 indirect 修饰符标记的枚举用例必须有一个关联值

//用枚举表示链表结构
enum List<T>{
    case end
    //表示case使是引用来存储
    indirect case node(T, next: List<T>)
}

<!--也可以将indirect放在enum前-->
//表示整个enum是用引用来存储
indirect enum List<T>{
    case end
    case node(T, next: List<T>)
}

参考:《Swift语言中的关键字总结

枚举的大小

上面已经整体分析了当前枚举的 rawValue 的存取操作,刚才在基础枚举分析源码的时候发现⼀个问题,那就是当前在 init ⽅法中分配了⼀个连续的字符串数组,那么是不是意味着当前设置 rawValue 之后,我们当前枚举所占⽤的内存就是这个连续数组的⼤⼩? 

普通enum大小分析

  • 案例1:

  • 案例2:

  • 案例3:

从以上案例结果分析,说明enum就是以1字节存储在内存中的,这是为什么呢?我们来分析下

LLDB分析

所以当前枚举的步⻓1 字节,也就意味着如果在内存中连续存储 NoMean ,需要跨越⼀个字节的⻓度。⼀个字节也就是 8 位,最⼤可以表达的数字是多少? 255 对吧~,那这个时候可能⼜有⼀个问题,如果我有超过 255 个 Case 该怎么办?那么系统会⾃动默认⽤ UInt16 来

存储 Case ,依次类推~ ()

总结:

  • 如果enum中有原始值,即rawValue,其大小取决于case的多少,如果没有超过UInt8即255,则就是1字节存储case

  • 当只有一个case的情况下,size0,表示这个enum是没有分支,没有意义的

  • 当有两个及以上case时,此时的enum是有意义的,如果没有超过255,则case的步长是1字节,如果超过,则UInt8->UInt16...,以此类推

关联enum的大小分析

从打印结果可以说明 enum中有关联值时,其内存大小取决于关联值的大小,``enum有关联值时,关联值的大小 取 对应枚举关联值 最大的,例如circle中关联值大小是8,而rectangle中关联值大小是16,所以取16。所以enum的size = 最大关联值大小 + case(枚举值)大小 = 16 + 1 = 17,而stride由于8字节对齐,所以自动补齐到24

总结:

  • 有关联值的enum大小,取决于最大case的内存大小【枚举大小的本质】

  • 关联值枚举的大小 = 最大case的内存大小 + 1(case的大小)

  • stride 表示 对齐后的大小(内存空间中真实占用的大小,8字节对齐)

结构体嵌套enum的大小分析

  • 案例1

  • 案例2

  • 案例3:

总结:

  • 如果结构体中嵌套了enum,但是没有声明变量,此时的size是0,stride是1

  • 如果结构体中没有其他属性,只有枚举变量,那么结构体的大小就是枚举的大小,即size为1

  • 如果结构体中还有其他属性,结构体的大小取决于元素的内存对齐三原则计算得出

总结:

  • enum的模式匹配方式,主要有两种:switch / if case

  • enum中还可以包含计算属性、类型属性,但是不能包含存储属性

  • enum中可以定义实例 + static修饰的方法

  • 具有关联值的枚举,可以成为三无enum,因为没有别名RawValue、init、计算属性rawValue

  • 普通enum的内存大小一般是1字节,如果只有一个case,则为0,表示没有意义,如果case个数超过255,则枚举值的类型由UInt8->UInt16->UInt32...

  • 具有关联值的enum大小,取决于最大case的内存大小+case的大小(1字节)

  • 结构体嵌套enum,如果没有属性,则size为0,如果只有enum属性,size为1,如果还有其他属性,则按照OC中内存对齐原则进行计算