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

推荐阅读更多精彩内容

  • C语言枚举 一周七天可以写成 第⼀个枚举成员的默认值为整型的 0,后⾯的枚举值依次类推,如果我们想更改,只需要这样...
    Mjs阅读 223评论 0 1
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,826评论 0 6
  • 枚举入门 基本定义 枚举作为 Swift 的 一等类型, 我们正好可以通过枚举, 来 一一列举 Swift 其它 ...
    overla5阅读 281评论 0 2
  • 基本使用 1、从语法上看,对比我们熟悉的其他语言,每个成员前面多了一个 case关键字。2、并且跟我们熟悉的另一点...
    swift加oc阅读 1,790评论 0 1
  • 首先感觉Swift的枚举确实相对OC来说功能和语法都扩展很多。首先说一下枚举的定义。 枚举声明的类型是囊括可能状态...
    正直走阅读 292评论 0 0