从零学习Swift 02:枚举和可选项

一:枚举

  1. 枚举的定义
    Swift中的枚举定义枚举类型很简单:
//枚举
enum Season{
    case spring
    case summer
    case autumn
    case winter
}

//也可以这样定义
enum Season{
    case spring,summer,autumn,winter
}

OC不同的是,Swift中的枚举不单单可以是Int类型,也可以是其他类型

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


var score = Score.points(90)
print(score) //打印 points(90)
score = .grade("A")
print(score) //打印 grade("A")

枚举Score的两个成员pointsgrade分别关联了Int类型和Character类型.枚举的关联值是存储在枚举变量的内存中的,也就是说90A都保存在枚举变量score的内存中.

枚举的关联值都存储在枚举变量中,因为枚举的关联值是不断变化的,每一个枚举变量都有对应的关联值,比如:

var score1 = Score.points(90)
var score2 = Score.points(78)
var score3 = Score.points(59)
var score4 = Score.grade("D")

90,78,59,D分别对应4个枚举变量,这些值都要保存在各自枚举变量的内存中.

  1. 原始值 (Raw Values)
    枚举成员可以用相同类型的默认值预先对应,这个默认值就是原始值:
//原始值
enum Mood: Character{
    case smile = "😀"
    case cry = "😭"
    case heart = "😍"
}

我们可以使用rawValue访问枚举成员的原始值:

如果枚举的原始值类型是Int , String,Swift 会自动为枚举成员分配原始值,成员是什么原始值就是什么:

如果是Int类型,原始值默认是从0开始,如果设置了某一个成员的原始值,那么下一个成员的原始值递增:

  1. 枚举内存空间
    要了解枚举的内存空间是如何分配的,我们需要先搞清楚3个方法:
    4.1 MemoryLayout.stride(ofValue:): 系统分配了多少内存; 等价于 MemoryLayout<Int>.stride
    4.2 MemoryLayout.size(ofValue:): 实际占用了多少内存; 等价于MemoryLayout<Int>.size
    4.3 MemoryLayout.alignment(ofValue:): 多少字节对齐; 等价于MemoryLayout<Int>.alignment
    以有关联值的枚举类型Score为例,分别打印一下他的内存大小:
enum Score{
    case points(Int)
    case grade(Character)
}


var score = Score.points(90)

print("Character 类型占用多少字节内存: \(MemoryLayout<Character>.size)")

// 系统分配了多少内存 24
print(MemoryLayout.stride(ofValue: score))

// 实际占用了多少内存 17
print(MemoryLayout.size(ofValue: score))

// 多少字节对齐 8
print(MemoryLayout.alignment(ofValue: score))

打印结果如下:
Character 类型占用多少字节内存: 16
24
17
8

从打印结果可以看到,系统给Score分配了24个字节.但是它实际只占用了17个字节,并且是以8字节对齐的.
分析一下这个24个字节是怎么来的?首先Int类型在Swift中是占用8个字节的.Character占用16个字节,难道系统分配的24个字节是Int+Character算出来的?当然不是这样!这个24是Character的16 个字节 加上一个标识位 1 个字节 , 也就是 17 然后再以 8 字节对齐后的结果.
这个标识位是怎么来的呢?
因为case points(Int)情况是需要8个字节来存储的;而case grade(Character)是需要16个字节来存储的.但是不管是哪种情况 16 个字节都够存储.所以case points(Int)case grade(Character)都存储在这16个字节中.但是都共用这 16 个字节,系统怎么区分是case points(Int)还是case grade(Character)呢?所以这就需要一个额外的标识位来区分是哪种成员.

  • 再来看看下面这种情况:
//枚举
enum Season{
    case spring
    case summer
    case autumn
    case winter
}

var season = Season.autumn

// 系统分配了多少内存 
print(MemoryLayout.stride(ofValue: season))

// 实际占用了多少内存
print(MemoryLayout.size(ofValue: season))

// 多少字节对齐 
print(MemoryLayout.alignment(ofValue: season))

打印结果
1
1
1

打印结果全是1,像这种简单的枚举,系统只需要1个标识位来标识哪种情况就可以了.根本就不需要额外的空间.

  • 如果加上原始值呢?
//枚举
enum Season: String{
    case spring
    case summer
    case autumn
    case winter
}

var season = Season.autumn

// 系统分配了多少内存
print(MemoryLayout.stride(ofValue: season))

// 实际占用了多少内存
print(MemoryLayout.size(ofValue: season))

// 多少字节对齐
print(MemoryLayout.alignment(ofValue: season))

打印结果
1
1
1

打印结果还是1.因为原始值的成员是固定的,是不会改变的,比如:

var season1 = Season.autumn
var season2 = Season.autumn
var season3 = Season.autumn

season1, season2, season3的原始值都是autumn,既然如此,编译器肯定不会让枚举变量都存储一个相同的值,这是很浪费内存的.况且根本也不需要存储,只需要1个字节的标识位来区分具体是哪种情况就好了.

有人可能会疑惑,那枚举的原始值存储在什么地方呢?其实我们根本就不用纠结这个问题,也许原始值根本就没有被存储,比如说原始值的rawValue可能是这样实现的:

enum Season: String{
    case spring
    case summer
    case autumn
    case winter
    
   //匹配后直接返回 
    func rawValue() -> String{
        switch self {
        case 0:
            return "spring"
        case 1:
            return "summer"
        case 2:
            return "autumn"
        case 3:
            return "winter"
        }
    }

}

总结:
枚举的关联值会占用枚举变量的内存,因为关联值是变化的;而枚举的原始值不会占用枚举变量的内存.

二:可选项 (Optional)

可选项也就是可选类型,在类型后面加一个问号 ?来定义一个可选项,它允许将值设置为nil.
如果不是可选项类型,是不允许赋值nil的:

非可选项不允许设置 nil

可选项的初始值就是nil,下面两句代码是等价的:

var age: Int?
var age: Int? = nil
  1. 强制解包
  • 我们可以吧可选项理解为一个盒子,是对其他类型的一种包装:
    如果为nil,就是一个空盒子
    如果不为nil,盒子里装的就是:被包装类型的数据

被包装的数据是不能直接使用的,他是Optional类型:

我们要想使用被包装的数据,就需要使用感叹号 !来进项强制解包:

需要注意的是,如果对值为nil(空盒子)进行强制解包,会报错:

所以我们在解包时就要判断可选项是否包含值,以免报错.

var age: Int?
if age != nil{
    print("age 有值")
}else{
    print("age 为nil")
}

还有一种更方便的方法判断可选项是否有值:可选项绑定

  1. 可选项绑定
var age: Int? = 10

if let age = age{
    print(age)
}else{
    print("age 为 nil")
}

可以使用if let age = age进行可选项绑定判断可选项是否有值,如果可选项有值就自动解包,并把解包后的值赋给临时的常量 let 或者 变量 var,并返回 true,否则返回false
需要注意的是,可选项绑定不能和&&预算符一起使用:

如果在可选项绑定时需要判断多个条件同时满足,可以用逗号 ,关联:

  1. 空合并运算符 ??
    我们可以用两个问号??来表示空合并运算符,它的格式是:
  • a ?? b
    . 要求如下:
    . 1: a必须为可选项,b可以是可选项也可以是非可选项.
    . 2: ba的存储类型必须相同
  • 运算规则如下:
    . 1: 如果a不为nil就返回a,返回a的同时还要看看b是不是可选项.如果b不是可选项,返回a时将自动解包
    . 2: 如果anil就返回b

可能有些绕,我们做一下联系熟悉一下:

示例1:左侧不是可选项


示例2:左侧和右侧类型不一致


示例3:a为非空可选项,b为非可选项

示例4:a为非空可选项,b为可选项

总结:空合并运算符的返回结果取决于b,如果b是可选类型,返回的结果就是可选类型;如果b不是可选类型,返回的结果就不是可选类型.

  1. ??if let配合使用,
    如果我们需要判断a , b任何一个有值都为true,之前我们都是这样:
var a: Int?
var b: Int?

if a != nil || b != nil{
    print("a,b至少有一个有值")
}else{
    print("a,b都为nil")
}

我们可以使用可选项绑定和??配合使用:

var a: Int?
var b: Int?

if let _ = a ?? b{
    print("a,b至少有一个有值")
}else{
    print("a,b都为nil")
}

  1. guard语句
    guard语句特别适合用来提前退出,它的语法如下:

    解释:
    . 1: 当guard语句的条件为false时,就会执行大括号里面的代码,和 if语句相反.
    . 2: 当guard语句的条件为true时,就会跳过guard语句

我们之前写登录语句时,通常都会像这样写:


而用guard语句实现起来就会简单很多:


var userInfo = ["userName": "Tom" , "passWord": "123456"]

func login2(user:[String : String]){
    
    //guard 语句解包后的 userName 作用域是整个方法体的,所以不用定义临时变量存储
    guard let userName = user["userName"] else {
        print("用户名为空")
        return
    }
    guard let passWard = user["passWord"] else {
        print("密码为空")
        return
    }
    //走到这里,就说明用户名和密码都有值
    
    print("登录成功,用户名\(userName),密码:\(passWard)")

}


login2(user: userInfo)

注意:如果 guard 语句条件为 false 进入大括号后,最后一定不要忘了退出作用域.

  1. 隐式解包
    如果我们能确定一个可选项肯定有值,就不需要每次再去判断可选项是否为nil.可以在类型后面加一个感叹号 !,让系统自动去解包,这就是隐式解包

  2. 字符串插值
    可选项在字符串插值或者打印时,编译器会发出警告,有三种方法可以消除警告:


    警告

方法一:强制解包


方法二:字符串的describing方法

方法三:空合并运算符 ??

  1. 多重可选项 ??
    多重可选项和空合并运算符都是两个问号 ?,它的意思是包装了盒子的盒子.
    比如说如下代码:
var age1: Int? = 10
var age2: Int?? = age1
var age3: Int?? = 10

他们的本质如下:


多重可选项

事实上age2age3是相等的:

但是如果像这样一开始就赋值nil,那么age2age3就不是相等的了:

var age1: Int? = nil
var age2: Int?? = age1
var age3: Int?? = nil

print(age2 == age3) //false

他们的本质如下:


知道他们的本质了,我们来练习一下:

var age1: Int? = nil
var age2: Int?? = age1
var age3: Int?? = nil


let num1 = (age2 ?? 1) ?? 2
let num2 = (age3 ?? 1) ?? 2

思考一下num1,num2的值分别是什么?
我们从左至右一步步拆解:age2 ?? 1,age2是一个大盒子里面包装着一个小的空的盒子,所以,age2不为空(因为它里面有一个空盒子,有东西就不为空),所以返回age2,而??右边是 1 ,也就是非可选项,所以age2会解包为age1,也就是nil,然后nil ?? 2 最后返回 2,所以num1的值为 2. num2的值大家自己分解分解.

我们还可以通过LLDB指令frame variable -R 简写(fr v -R)查看他们的结构:

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

推荐阅读更多精彩内容