swift进阶十二:枚举enum

swift进阶 学习大纲

本节,分析枚举enum

  1. 各语言枚举区别
  2. swift枚举的使用
  3. swift枚举大小
  4. 枚举的嵌套
  5. 枚举的递归(indirect)
  6. OC桥接
  7. SIL分析

1. 各语言枚举区别

1.1 C语言枚举

  • 仅支持Int类型,默认首元素值为0,后续元素值依次+1
    如果中间元素赋值,以赋值为准,后续没赋值元素值依旧依次+1
enum WEEK {
    Mon, Tue = 10, Wed, Thu, Fri, Sat, Sun
};

enum WEEK a = Mon;
enum WEEK b = Wed;
printf("%d",a);  // 打印0
printf("%d",b);  // 打印11

OC语言枚举类型C语言一致

1.2 Swift枚举

十分强大

    1. 格式: 不用逗号分隔,类型需使用case声明
    1. 内容:
    • 支持IntDoubleString基础类型,有默认枚举值
      String类型默认枚举值key名Int、Double数值型默认枚举值0开始+1递增 )
    • 支持自定义选项
      不指定支持类型没有rawValue。但同样支持case枚举,可自定义关联内容
  • 指定类型:
    没指定枚举值时,各类型都有默认枚举值
// Double类型
// CaseIterable协议,有allCases属性,支持遍历所有case
enum Week1: Double, CaseIterable {
    case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}
Week1.allCases.forEach { print($0.rawValue)}

// String类型
enum Week2: String, CaseIterable {
    case Mon,Tue, Wed, Thu, Fri, Sat, Sun
}

Week2.allCases.forEach { print($0.rawValue)}
swift进阶十二:枚举enum_第1张图片
image.png
  • 自定义类型(强大
    不指定枚举类型,可给枚举项添加拓展内容switch读取时,可提取出拓展类型,进行相应操作。
// 自定义类型的使用
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

func printValue(_ v: Shape) {
    // switch区分case(不想每个case处理,可使用default)
    switch v {
    case .square(let width):
        print(width)
    case .circle(let radius, _):
        print(radius)
    }
}

let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)
printValue(s)
printValue(c)
swift进阶十二:枚举enum_第2张图片
image.png

2. swift枚举的使用

  • swift枚举的读取,有两种方式:

1.统一使用switch区分case

  1. 判断单类case,直接使用if语句

2.1 switch方式

  • 灵活的属性读取
  1. let声明整个case
  2. 分开声明case中的每个关联内容
  3. 合并case,使用同一个变量x
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

func printValue1(_ v: Shape) {
    switch v {
    // 1. let声明整个case
    case let .square(x):
        print(x)
    // 2. 分开声明case中的每个关联内容
    case .circle(let x, var y):
        y += 100  // var声明的变量,可修改和赋值
        print("radius: \(x), borderWidth: \(y)")
    }
}

func printValue2(_ v: Shape) {
    switch v {
    // 3. 合并case,使用同一个变量x
    case let .square(x), let .circle(x, _):
        print(x)
    }
}

let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)

print("------")
printValue1(s)
printValue1(c)

print("------")
printValue2(s)
printValue2(c)
swift进阶十二:枚举enum_第3张图片
image.png

2.2 if 方式

  • 判断是否为指定case项,并获取关联内容
    (同样支持整体case关联内容声明分开声明
// 自定义类型的使用
enum Shape {
    case square(width: Double)
    case circle(radius: Double, borderWidth:Double)
}

let s = Shape.square(width: 10)
let c = Shape.circle(radius: 20, borderWidth: 1)

// 判断s是否是square类型。并获取`关联内容`
// 1. 内部声明关联内容类型(如: let)
if case .square(let width) = s {
    print(width)
}

// 2. 声明case所有关联内容类型(如: var)
if case var .circle(radius, borderWidth) = c {
    radius += 200
    borderWidth += 100
    print(radius, borderWidth)
}
swift进阶十二:枚举enum_第4张图片
image.png

2.3 计算型属性 & 函数

  • enum枚举支持计算型属性函数
enum Direct: Int {
    case up
    case down
    case left
    case right
    
    // 计算型属性
    var description: String{
        switch self {
        case .up:
            return "这是上面"
        default:
            return "这是\(self)"
        }
    }
    
    // 函数
    func printSelf() {
        print(description)
    }
}

Direct.down.printSelf() // 打印: 这是down

3. swift枚举大小

size: 实际占用内存大小
stride:系统分配的内存大小

指定类型:

  • 一个case项:size0高版本xcode可能为1 ),stride1
  • 多个case项:
    case小于255个: size1stride1
    超过255个会自动扩容sizestride都会增加
    (原因,1字节(8bit)可区分255种情况。所以默认size1,当只有一个case时,0x0可表示。所以size01都可理解)
swift进阶十二:枚举enum_第5张图片
image.png

自定义:

  • 占用内存空间最大case大小 + enum自身大小:
    swift进阶十二:枚举enum_第6张图片
    image.png

    如果不清楚Foo2case大小size为何为18,可查看内存对齐

顺带提供一个struct(5个属性值)大小计算方式

swift进阶十二:枚举enum_第7张图片
MyStruct1 内存计算

4. 枚举的嵌套

  • 枚举的嵌套本质上只是在不同作用域创建,并没有造成结构上嵌套

4.1 enum嵌套enum

enum Foo {
    
    enum Direct: Int {
        case up
        case down
        case left
        case right
    }
    
    case leftUp(element1: Direct, element2: Direct)
    case rightDown(element1: Direct, element2: Direct)
}

var f = Foo.leftUp(element1: .left, element2: .up)

4.2 struct嵌套enum

struct Foo {
    
    enum Direct: Int {
        case up
        case down
        case left
        case right
    }
    
    let key: Direct
    
    func printKey() {
        switch key {
        case .up:    print("上")
        case .down:  print("下")
        default:
            print("其他")
        }
    }
    
}

var f = Foo(key: .down)
f.printKey()  // 打印: 下

5. 枚举的递归(indirect)

  • 枚举中case关联内容使用自己枚举类型,是否会造成递归枚举大小如何确定?

案例:

  • 树节点,需要重复使用:
    image.png
  • 直接使用XCode报错
    (因为直接使用enum大小需要case确定,而case大小又需要使用到enum大小。所以无法计算大小,报错)
  • swift提供了indirect关键字,可以标记递归枚举,也可以标记单个case,被标记后,case项直接去堆中申请内存,变为引用类型,大小为指针8字节
swift进阶十二:枚举enum_第8张图片
image.png
  • 输出SIL中间代码,可以看到是使用alloc_box创建枚举值,内部调用了swift_allocObject,去申请空间:
    swift进阶十二:枚举enum_第9张图片
    image.png

在SIL官方文档中,有介绍alloc_box:

swift进阶十二:枚举enum_第10张图片
image.png

汇编层也可佐证

swift进阶十二:枚举enum_第11张图片
image.png

6.OC桥接

  • OC枚举仅支持Int类型,而swift支持多种类型

6.1 OC使用swift枚举:

    1. swift中创建Int类型枚举值,使用@objc声明
      swift进阶十二:枚举enum_第12张图片
      image.png
    1. @objc声明后,桥接文件中,自动生成了OCSWIFT_ENUM:
      swift进阶十二:枚举enum_第13张图片
      image.png
    1. OC文件中,导入swift桥接头文件,直接调用SwiftEnum
      swift进阶十二:枚举enum_第14张图片
      image.png

6.2 swift使用OC枚举:

  1. OC.h头文件声明枚举类型:
  • typedef NS_ENUM(NSUInteger, OCEnum):自动转换成swift枚举
  • typedef enum:转换成结构体
    swift进阶十二:枚举enum_第15张图片
    image.png
  1. 桥接文件中,添加OC头文件:

    swift进阶十二:枚举enum_第16张图片
    image.png

  2. 自动生成swift文件中,可以看到转换的类型:

    swift进阶十二:枚举enum_第17张图片
    image.png

    swift进阶十二:枚举enum_第18张图片
    image.png

  3. swift文件中使用:

  • NS_ENUM生成:可正常使用
  • typedef enum生成:只能通过通过值初始化,再使用
    swift进阶十二:枚举enum_第19张图片
    image.png

6.3 OC使用swift枚举:

  • 如果swift不是Int类型,而又希望OC能用,只能自己做个桥接

(例如: 原本swift枚举类型String,可直接通过rawValue读取值。
为了兼容OC,把类型改成Int,自定义计算型属性,禁止使用默认的rawValue读取)

  • swift中创建int类型枚举,自定义string计算属性,并禁止rawValue的使用。

    swift进阶十二:枚举enum_第20张图片
    image.png

  • OC直接使用

    swift进阶十二:枚举enum_第21张图片
    image.png

  • 如果还希望OC能访问到swift对应的String值:

使用class的类方法兼容

  • class需要继承NSObject类函数完成枚举String的映射。enum禁止rawValue,改用string计算属性获取

    swift进阶十二:枚举enum_第22张图片
    image.png

  • 桥接文件中可以看到生成了OC类方法枚举:

    swift进阶十二:枚举enum_第23张图片
    image.png

    swift进阶十二:枚举enum_第24张图片
    image.png

  • OC文件中使用:

    swift进阶十二:枚举enum_第25张图片
    image.png

7. SIL分析

7.1 enum格式

  • 案例代码:
enum Week: String {
    case Mon
    case Tue
    case Wed
    case Thu
    case Fri
    case Sat
    case Sun
}
  • 打开终端,cd当前文件夹swiftc -emit-sil main.swift > ./main.sil命令输出SIL文件:

取消swift函数名的混淆输出: swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil

swift进阶十二:枚举enum_第26张图片
image.png

7.2 rawValue的读取

  • SIL文件中,搜索rawValuegetter方法:
    switch跳转指定case,执行函数,得到case内容,返回case内容
    swift进阶十二:枚举enum_第27张图片
    image.png

有2个疑问:

  1. 默认属性(字符串)是什么时候创建的?
  2. 如何记录case名对应值的?
  1. 默认属性(字符串)是什么时候创建的?

编译期就会生成所有符号

swift进阶十二:枚举enum_第28张图片
image.png

  • 所以上面rawValue读取时,可直接通过string_literalMachO读取字符
  1. 如何记录case名对应值的?
  • String类型枚举caserawValue值(打印结果一样,但类型是对应枚举类型String
  • 通过rawValue初始化case时,类型为Option找不到对应case时,为nil
enum Week: String {
   case Mon
   case Tue
   case Wed
   case Thu
   case Fri
   case Sat
   case Sun
}

// case与rawValue值(打印结果一样,但类型不同)
print("类型:\(type(of: Week.Mon)) 值:\(Week.Mon)")                   // 打印: 类型:Week   值:Mon
print("类型:\(type(of: Week.Mon.rawValue)) 值:\(Week.Mon.rawValue)") // 打印: 类型:String 值:Mon

// 通过rawValue来生成对应的case(可选类型,找不到rawValue对应的case,就是nil)
print(Week.init(rawValue: "Mon"))    // 打印: Optional(Demo.Week.Mon)
print(Week.init(rawValue: "Hello"))  // 打印: nil
  • 通过SIL分析init(rawValue:):

    image.png

    完整流程

  • 1.创建:
    创建枚举(格式:元组(Array,Pointer),此例中TString) ,再遍历创建所有枚举项。

  • 2.查询:
    通过_findStringSwitchCase 获取入参值index,使用switch通过index(int类型)匹配到case,匹配成功:返回optional的some值,匹配失败,直接返回nil

  • swift源码中搜索findStringSwitchCase,可以看到是通过for遍历匹配index

    swift进阶十二:枚举enum_第29张图片
    image.png


enum枚举较为简单,我们了解了与其他语言差异用法,顺带探索大小源码实现

  • 下一节,介绍swift闭包

你可能感兴趣的:(swift进阶十二:枚举enum)