Swift语法 Swift5 【04 - 枚举】


  • 作者: Liwx
  • 邮箱: 1032282633@qq.com
  • 源码: 需要源码的同学, 可以在评论区留下您的邮箱

iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

00 - 汇编
01 - 基础语法
02 - 流程控制
03 - 函数
04 - 枚举
05 - 可选项
06 - 结构体和类
07 - 闭包
08 - 属性
09 - 方法
10 - 下标
11 - 继承
12 - 初始化器init
13 - 可选项


目录

  • 01-枚举的基本用法
  • 02-关联值(Associated Values)
  • 03-关联值举例
  • 04-原始值(Raw Values)
  • 05-隐式原始值(Implicitly Assigned Raw Values)
  • 06-递归枚举(Recursive Enumeration)
  • 07-内存布局(MemoryLayout)

01-枚举的基本用法

// 写法1
enum Direction {
    case north
    case south
    case east
    case west
}

// 写法2
enum Direction {
    case north, south, east, west
}

var dir = Direction.west
dir = Direction.east
dir = .north
print(dir)  // north

switch dir {
case .north:
    print("north")  // north
case .south:
    print("south")
case .east:
    print("east")
case .west:
    print("west")
}

02-关联值(Associated Values)

  • 关联值
    • 将枚举的成员值其他类型的值关联存储在一起
enum Score {
    case points(Int)
    case grade(Character)
}

var score = Score.points(96)
score = .grade("A")

switch score {
case let .points(i):
    print(i, "points")
case let .grade(i):
    print("grade", i) 
} // grade A
  • 必要时let也可以改为var
enum Date {
    case digit(year: Int, month: Int, day: Int)  // 可使用标签定义
    case string(String)  
}

var date = Date.digit(year: 2020, month: 1, day: 2)
date = .string("2020-01-03")
switch date {
case .digit(var year, let month, let day):  // year var
    print(year, month, day)
case let .string(value):
    print(value)
}  // 2020-01-03

03-关联值举例

enum Password {
    case number(Int, Int, Int, Int)
    case gesture(String)
}

var pwd = Password.number(3, 5, 7, 8)
pwd = .gesture("12369")

switch pwd {
case let .number(n1, n2, n3, n4):
    print("number is ", n1, n2, n3, n4)
case let .gesture(str):
    print("gesture is ", str)
}

04-原始值(Raw Values)

  • 原始值
    • 枚举成员可以使用相同类型的默认值与之对应, 这个默认值叫做: 原始值
    • 注意: 原始值不占用枚举变量的内存
// PokerSuit的原始值类型为Character
enum PokerSuit : Character {  
    case spade = "♠"
    case heart = "♥"
    case diamond = "♦︎"
    case club = "♣︎"
}

var suit = PokerSuit.spade
print(suit) // spade
print(suit.rawValue) // ♠
print(PokerSuit.club.rawValue) // ♣︎
// Grade的原始值类型为String
enum Grade : String {
    case perfect = "A"
    case great = "B"
    case good = "C"
    case bad = "D"
}

print(Grade.perfect.rawValue)   // A
print(Grade.great.rawValue)     // B
print(Grade.good.rawValue)      // C
print(Grade.bad.rawValue)       // D

05-隐式原始值(Implicitly Assigned Raw Values)

  • 如果枚举的原始值类型IntString, Swift会自动分配原始值

  • String类型自动分配原始值

// 写法1
enum Direction : String {
    case north = "north"
    case south = "south"
    case east = "east"
    case west = "west"
}

// 写法2 等价于写法1
enum Direction : String {
    case north, south, east, west
}
print(Direction.north)  // north
print(Direction.north.rawValue) // north
  • Int类型自动分配原始值
enum Season : Int {
    case spring, summer, autumn, winter
}
print(Season.spring.rawValue)   // 0
print(Season.summer.rawValue)   // 1
print(Season.autumn.rawValue)   // 2
print(Season.winter.rawValue)   // 3
  • Int类型自定义原始值
enum Season : Int {
    case spring = 1, summer, autumn = 4, winter
}
print(Season.spring.rawValue)   // 1
print(Season.summer.rawValue)   // 2
print(Season.autumn.rawValue)   // 4
print(Season.winter.rawValue)   // 5

06-递归枚举(Recursive Enumeration)

  • 递归枚举定义关键字 indirect
    • 递归枚举即枚举成员存在递归调用的枚举类型
  • 枚举内部成员添加indirect关键字, 在调用本身的内部成员前加indirect
enum ArithExpr {
    case number(Int)
    indirect case sum(ArithExpr, ArithExpr)
    indirect case difference(ArithExpr, ArithExpr)
}
  • 枚举外部添加indirect关键字
indirect enum ArithExpr {
    case number(Int)
    case sum(ArithExpr, ArithExpr)
    case difference(ArithExpr, ArithExpr)
}

// 递归枚举使用
let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)

func calculate(_ expr: ArithExpr) -> Int {
    switch expr {
    case let .number(value):
        return value
    case let .sum(left, right):
        return calculate(left) + calculate(right)  // 枚举递归调用
    case let .difference(left, right):
        return calculate(left) - calculate(right)  // 枚举递归调用
    }
}

calculate(difference)   // 7

07-内存布局(MemoryLayout)

  • 使用MemoryLayout获取数据类型占用的内存大小
enum Password {
    case num(Int, Int, Int, Int)
    case other
}

MemoryLayout<Password>.stride   // 40, 分配占用的空间大小
MemoryLayout<Password>.size     // 33, 实际用的空间大小
MemoryLayout<Password>.alignment// 8, 内存对齐

var pwd = Password.num(9, 8, 6, 4)
pwd = .other
MemoryLayout.stride(ofValue: pwd)// 40, 分配占用的空间大小
MemoryLayout.size(ofValue: pwd) // 33, 实际用的空间大小
MemoryLayout.alignment(ofValue: pwd)// 8, 内存对齐

  • iOS内存布局: 小端

  • 查看内存方式

    • 查看内存方式1: 底部终端左侧窗口,选择要查看的变量/常量,右键菜单-View Memory of "变量/常量名"
    • 查看内存方式2: Debug - DebugWorkflow - View Memory, 快捷键control+option+commond+shift+M 放开后再按enter
    • 使用Mems工具打印内存地址 GitHub链接

  • 简单查看常量/变量内存布局
var a = 10
print(a)    // 这边打断点观察
// Int型变量a内存分配8字节
// 0A 00 00 00 00 00 00 00
image.png

  • 查看枚举内存布局
enum TestEnum {
    case test1, test2, test3
}

var t = TestEnum.test1  // 内存: 00
t = .test2  // 内存: 01
t = .test3  // 内存: 02

print(Mems.ptr(ofVal: &t))  // 内存: 02

print(MemoryLayout<TestEnum>.size)  // 这边打断点观察  1
print(MemoryLayout<TestEnum>.stride)    // 1
print(MemoryLayout<TestEnum>.alignment) // 1
image.png

  • 枚举原始值内存查看
    • 枚举类型后面冒号 : 后面的类型表示原始值类型
    • 原始值不影响枚举变量内存存储
enum TestEnum1 : Int {  // 原始值为Int
    case test1 = 1, test2 = 2, test3 = 3
}

var t1 = TestEnum1.test1    // 内存: 00
print(Mems.ptr(ofVal: &t1))
t1 = .test2 // 01
t1 = .test3 // 02
// - 原始值不影响枚举变量内存存储

print(MemoryLayout<TestEnum1>.size)         // 1 断点观察
print(MemoryLayout<TestEnum1>.stride)       // 1
print(MemoryLayout<TestEnum1>.alignment)    // 1
image.png

  • 枚举关联值内存查看
    • 1个字节存储成员值(索引值)
    • N个字节存储关联值(N: 占用内存最大的关联值),任何一个case的关联值都共用这N个字节
enum TestEnum2 {
    case test1(Int, Int, Int)
    case test2(Int, Int)
    case test3(Int)
    case test4(Bool)
    case test5
}

print(MemoryLayout<TestEnum2>.size)         // 25
print(MemoryLayout<TestEnum2>.stride)       // 32
print(MemoryLayout<TestEnum2>.alignment)    // 8

var t2 = TestEnum2.test1(1, 2, 3)
print(Mems.ptr(ofVal: &t2))
// 01 00 00 00 00 00 00 00
// 02 00 00 00 00 00 00 00
// 03 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00// 成员值,类似索引值

// Mems.memStr内存里面的内容
print(Mems.memStr(ofVal: &t2))
// 0x0000000000000001 0x0000000000000002 0x0000000000000003 0x0000000000000000

print(Mems.memStr(ofVal: &t2, alignment: .one))
// 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

t2 = .test2(4, 5)
// 04 00 00 00 00 00 00 00
// 05 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 01 00 00 00 00 00 00 00// 成员值,类似索引值

t2 = .test3(6)
// 06 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 02 00 00 00 00 00 00 00// 成员值,类似索引值

t2 = .test4(true)
// 01 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 03 00 00 00 00 00 00 00// 成员值,类似索引值

t2 = .test5
// 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00
// 04 00 00 00 00 00 00 00// 成员值,类似索引值

print(t2)

  • 如果枚举只有一个成员值, 实际没有用到内存
enum TestEnum3 {
    case test
}

var t3 = TestEnum3.test
print(Mems.ptr(ofVal: &t3)) // 0x0000000000000001
print(t3)

print(MemoryLayout<TestEnum3>.size)         // 0 实际没有用到内存
print(MemoryLayout<TestEnum3>.stride)       // 1
print(MemoryLayout<TestEnum3>.alignment)    // 1

  • 枚举只有一个关联值的成员值内存查看
    • 无需1个字节存储成员值
enum TestEnum4 {
    case test(Int)
}

var t4 = TestEnum4.test(10)
print(Mems.ptr(ofVal: &t4))
print(t4)

print(MemoryLayout<TestEnum4>.size)         // 8
print(MemoryLayout<TestEnum4>.stride)       // 8
print(MemoryLayout<TestEnum4>.alignment)    // 8

  • 枚举的switch语句底层是如何实现的?
  • 通过枚举的成员值(类似索引值)来判断要执行哪个case
enum TestEnum5 {
    case test1(Int, Int, Int)
    case test2(Int, Int)
    case test3(Int)
    case test4(Bool)
    case test5
}

// - 通过枚举的成员值(类似索引值)来判断要执行哪个case
// - TestEnum5.test2(10, 20) 仅仅只是内存赋值操作, 不存在函数调用
var t5 = TestEnum5.test2(10, 20)
switch t5 {
case let .test1(v1, v2, v3):
    print("test1", v1, v2, v3)
case let .test2(v1, v2):
    print("test2", v1, v2)
case let .test3(v1):
    print("test3", v1)
case let .test4(v1):
    print("test4", v1)
case let .test5:
    print("test5")
} // test2 10 20
image.png

iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

下一篇: 05 - 可选项
上一篇: 03 - 函数


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