美文网首页swift进阶
swift进阶十二:枚举enum

swift进阶十二:枚举enum

作者: markhetao | 来源:发表于2020-12-22 15:15 被阅读0次

    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)}
    
    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)
    
    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)
    
    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)
    }
    
    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都可理解)
    image.png

    自定义:

    • 占用内存空间最大case大小 + enum自身大小:
      image.png
      如果不清楚Foo2case大小size为何为18,可查看内存对齐

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

    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字节
    image.png
    • 输出SIL中间代码,可以看到是使用alloc_box创建枚举值,内部调用了swift_allocObject,去申请空间:
      image.png

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

    image.png

    汇编层也可佐证

    image.png

    6.OC桥接

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

    6.1 OC使用swift枚举:

      1. swift中创建Int类型枚举值,使用@objc声明
        image.png
      1. @objc声明后,桥接文件中,自动生成了OCSWIFT_ENUM:
        image.png
      1. OC文件中,导入swift桥接头文件,直接调用SwiftEnum
        image.png

    6.2 swift使用OC枚举:

    1. OC.h头文件声明枚举类型:
    • typedef NS_ENUM(NSUInteger, OCEnum):自动转换成swift枚举
    • typedef enum:转换成结构体
      image.png
    1. 桥接文件中,添加OC头文件:

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

      image.png
      image.png
    3. swift文件中使用:

    • NS_ENUM生成:可正常使用
    • typedef enum生成:只能通过通过值初始化,再使用
      image.png

    6.3 OC使用swift枚举:

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

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

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

      image.png
    • OC直接使用

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

    使用class的类方法兼容

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

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

      image.png
      image.png
    • OC文件中使用:

      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

    image.png

    7.2 rawValue的读取

    • SIL文件中,搜索rawValuegetter方法:
      switch跳转指定case,执行函数,得到case内容,返回case内容
      image.png

    有2个疑问:

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

    编译期就会生成所有符号

    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<T>,Pointer),此例中TString) ,再遍历创建所有枚举项。

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

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

      image.png

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

    • 下一节,介绍swift闭包

    相关文章

      网友评论

        本文标题:swift进阶十二:枚举enum

        本文链接:https://www.haomeiwen.com/subject/dfclnktx.html