枚举
枚举的根本用法
在Swift中,通过enum
关键字来声明一个枚举,和结构体struct
相同,也是一个值类型,一同也可以增加方法、核算特色、也可以遵循协议(protocol
),支撑扩展(extension
)。
Swift中枚举的根本用法如下:
enum FWJEnum {
case Test_One
case Test_Two
case Test_Three
}
你也可以在同一个case中完成多个枚举
enum FWJEnum {
case Test_One,Test_Two,Test_Three
}
枚举原始值
在OC和C中,枚举主要支撑整数类型,鄙人面的比如中:A,B,C分别默许代表 0,1,2
typedef NS_ENUM(NSUInteger, LGEnum) {
A,
B,
C,
};
而在Swift中的枚举则愈加灵活,而且不需给枚举中的每一个成员都供应值。假设一个值(所谓“原始”值)要被供应给每一个枚举成员,那么这个值可以是字符串、字符、恣意的整数值,或许是浮点类型。
enum Color: String {
case red = "Red"
case amber = "Amber"
case green = "Green"
}
enum FWJEnum: Double {
case a = 10.0
case b= 20.0
case c = 30.0
case d = 40.0
}
隐式RawValue
在swift中,枚举的原始值可以通过类型推动机制自动设置,这种就是隐式RawValue
。我们通过下面这个案例来解说。
enum DayOfWeek: Int {
case mon, tue, wed, thu, fri = 10, sat, sun
}
print(DayOfWeek.mon.rawValue) // 0
print(DayOfWeek.tue.rawValue) //1
print(DayOfWeek.fri.rawValue)//10
print(DayOfWeek.sun.rawValue)// 12
这儿的mon
值是从0开端,但是当我们设置fri
指定为值10之后,后面的sat
、sun
的值就会在fri
设定的值的基础上进行累加,也就是说,sat
、sun
的值分别为11,12。
假设把枚举的值设定成string
类型时,那么当case
没有设定值时,系统编译器会自动把与case
同名的字符串当成这个case
值的原始值。你也可以手动设置case
原始值。
enum DayOfWeek: String {
case mon, tue, wed, thu, fri = "Fri", sat, sun
}
print(DayOfWeek.mon.rawValue) //mon
print(DayOfWeek.fri.rawValue) //Fri
print(DayOfWeek.sat.rawValue) // sat
那枚举原始值是如何获取这个字符串的呢?我们翻开sil文件去看一下。
enum DayOfWeek: String {
case mon, tue, wed, thu, fri = "Fri", sat, sun
}
let rawValue = DayOfWeek.mon.rawValue
我们先看一下enum
类型的声明
可以看到声明里面有一个可失利的初始化器init?(rawValue:String)
,以及一个只能get
的核算特色rawValue
。
我们接下来查看rawValue
的get
方法。
在上面的sil代码中,进行一次形式匹配swift_enum %0
,这儿%0
传入的是其时的枚举值。此时我们传入的是mon
,因此匹配到的代码就是case #DayOfWeek.mon!enumelt: bb1
,然后我们就进入了bb1
代码模块。
在bb1
代码块中,直接字符串创立了"mon"
,sil中关于枚举编译器会自动生成rawValue
核算特色,和初始化方法,核算特色实质是个方法,这一切在编译时就现已承认了每个枚举值对应的rawValue
值,也不需求存储。
那么这个字符串mon
是从哪里得到的哪?这样的字符串其实就是一个字符串常量,而字符串常量存储在哪里哪,我们把Mach-o文件拖到MachoView应用来查看一下。
枚举可失利初始化器
枚举里面的枚举值和原始值是两个不同的类型,我们运用上面的DayOfWeek
来做说明。比如枚举值mon
,它的类型就是DayOfWeek
。而原始值虽然默许值是mon
,但是它的类型却是String
类型。所以我们不能直接运用字符串去创立一个枚举值。在Swift中,供应了一个可失利初始化器init?(rawValue:String)
来初始化枚举值,接下来我们从sil文件中去了解一下枚举值是怎样初始化的。
通过sil可以发现,在调用init(rawValue: )
时 ,首要会把一切的case
对应的字符串存储进一个连续的内存空间。然后根据传入的rawValue
去和这个空间里面的字符串进行匹配得到枚举值,通过原始值进行枚举实例的结构时,是有或许结构失利的,因为传入的原始值不一定会对应某一个枚举值。因此,这个方法实践上回来的是一个Optional类型的可选值,假设结构失利,则会回来nil。
相关值
在Swfit中,枚举不仅仅是用来描绘一些简略的类型,您可以通过运用相关值和其它的数据类型进行相关,从而可以完成对一些凌乱数据模型的描绘。
形式匹配
Swift要求要匹配一切的case
,假设你不想匹配一切的case
,可以运用default
替代。
枚举的内存大小
前面我们知道了枚举的根本用法,还知道了它和可以有原始值和相关值,原始值是核算特色不会存储在内存中,所以对枚举的内存信息不会有影响,难么相关值对枚举的存储有什么影响呢,下面来分下面几种情况探究一下枚举值占有内存的大小
枚举值无相关值
我们先看一下无相关值的size
和stride
,这两个值都是为1。这个很好了解,因为枚举的隐式rawValue
作为硬编码现已存储在Mach-O里面了。因此现在枚举只需求存储枚举值就可以了。而关于其时的枚举值,Swift默许是以UInt8
类型来存储的,也就是1字节。我们接下来来看枚举值具体是怎样存储的。
通过打印a、b、c三个变量的内存。可以看到每一个枚举值内存中存储都是只需一个字节,而且三个连续的枚举值内存地址相差了1个字节,而且他们内存里面的值是0x0
、0x1
、0x2
这样累加起来存放到内存用来标识。也就是说一个枚举值大小为1字节,可以至少存储256个枚举值。
枚举值只需一个相关值
我们先来看一下只相关一个BOOL
值的情况
可以看到,每个枚举的大小(size
)和步长(stride
)都为1。这是因为Bool
类型是1字节,也就是UInt8
,所以其时能表达256个case
的情况,关于布尔类型来说,只需求运用低位的0, 1这两种情况,其他剩余的空间就可以用来表示没有负载的case
值。
通过内存地址打印,可以看到,不同的case
值确实是按照我们在开端得出来的那个结论进行布局的。
接下来我们看一下只需一个Int
相关值的情况
可以看到,和上面的BOOL
相关值情况不相同,这回每个枚举的大小(size
)为9,步长(stride
)为16。这是因为关于Int
类型的负载来说,其实系统是没有方法计算其时的负载所要运用的位数,也就意味着其时Int
类型的负载是没有额外的剩余空间的,这个时分我们就需求额外开辟内存空间来去存储我们的case
值,也就是 8 + 1 = 9 字节。
枚举值有多个相关值
我们先看一下这多个相关值都是同一个类型的情况
可以看到,每个枚举的大小(size
)和步长(stride
)都为1。这是因为里面的相关值类型都是BOOL
类型,而BOOL
类型的枚举大小我们上面现已说过了。
我们接下来看一下内存分布情况
这儿我们可以看到其时内存存储的分别是00, 01, 40, 41, 80,81
,这是因为关于bool
类型来说,我们存储的无非就是0
或1
,只需求用到1位,所以剩余的 7 位这儿我们都统称为common spare bits
,关于其时的case数量来说我们完全可以把一切的情况放到common spare bits
中,所以这儿我们只需求 1 字节就可以存储一切的内容了。
接下来我们来看一下00, 01, 40, 41, 80,81
分别代表的是什么?首要0,4,8这儿我们叫做tag value
,0,1
这儿我们就做tag index
,至于这个tag value
怎样来的,假设感兴趣的通过可以去阅读一下源码中的Enum.cpp
和GenEnum.cpp
这两个文件。
我们再看一下多个相关值是不同类型的情况下是什么样的
此时枚举的大小是9,步长是16。这是因为我们有多个相关值的枚举时,其时枚举类型的大小取决于其时最大相关值的大小。因此其时枚举的大小就等于sizeof(Int) + sizeof(rawVlaue)= 9
,步长大小根据内存对齐补到16。
假设是下面这种情况
那么其时枚举大小是sizeof(Int) * 3 + sizeof(rawVlaue)= 25
。
我们再看别的一个比如
可以看到,当相关值的类型次第产生改变后,枚举的大小也会不相同。第一个LGEnum
的大小为25,而LGEnum1
的大小变成了32。这是因为bool
类型的方位在中心时,枚举内部位了可以敏捷读取和存储每个相关值,进行了内存对齐,每个相关值都是8个字节,bool
类型会被补齐到8个字节, bool
类型的方位在结尾时,系统核算内存时,会清晰知道最后一位是bool
类型是1位,并不会因为没有对齐需求去做指针的移动,所以就是25个字节,但是根据内存对齐的原则,分配内存时会去给其时枚举变量补齐内存。
枚举内存大小总结
当枚举值有相关值时,它的存储大小和相关值类型和数量有关,当相关值类型有额外空间可以的值时,系统会把额外的空间运用起来存储 枚举值,当没有额外空间时,系统会按照相关值类型所需的大小 + 存储枚举值的一个字节
作为实践需求的长度,然后根据对齐原则做内存补齐。
单个枚举值
当一个枚举只需一个枚举值时,我们不需求用任何东西来去区别其时的枚举值,所以当我们打印其时的枚举大小你会发现是0。
递归枚举
递归枚举是具有另一个枚举作为枚举成员相关值的枚举。当编译器操作递归枚举时有必要插入间接寻址层。你可以在声明枚举成员之前运用indirect
关键字来清晰它是递归的。
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
你相同可以在枚举之前写indirect
来让整个枚举成员在需求时可以递归:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
接下来我们就可以通过上面定义的递归枚举完成一个表达式:(5+4)*2
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// Prints "18"
可选值
之前我们在写代码的进程中早就触摸过可选值,比如我们在代码这样定义:
class LGTeacher {
var age: Int?
}
其时的age
我们就称之为可选值。
我们也可以这样写可选值。
var age: Int? = var age: Optional<Int>
那关于Optional
的实质是什么?我们直接跳转到源码,翻开Optional.swift
文件
@frozen
public enum Optional<Wrapped> {
case none
case some(Wrapped)
}
可以看到,可选值Option
实践是一个枚举类型,当为空的时分,回来一个none
,当有值的时分,回来其时值。
可选值强制解绑
当你承认你定义的可选值有值时,你可以运用!
来对这个可选值进行强制解绑。
var age: Int? = 10
let age2 = age!
print(age2)
可选值绑定
我们可以通过if
句子来判别这个可选项是否有值,如下:
var age: Int?
if age == nil {
print("age is nil")
}else {
print("age is (age!)")
}
除了通过这种方法,我们还可以通过可选项绑定来判别可选项是否有值,而且取出来。假设可选项包括有值,会自动解包,把值赋给一个暂时的常量(let)或许变量(var),并回来一个 Bool 类型。
代码如下:
var age: Int? = 10
if let age = age {
print("age is (age)")
}else {
print("age is nil")
}
可选链
我们都知道再OC
中我们给一个nil
目标发送音讯什么也不会产生,Swift中我们是没有方法向一个nil
目标直接发送音讯,但是凭仗可选链可以到达相似的作用。我们看下面两段代码
let str: String? = "abc"
let upperStr = str?.uppercased() // Optional<"ABC">
var str1: String?
let upperStr1 = str?.uppercased() // nil
可以看到,当str
有初始值时,它就履行大写操作,回来一个Optional<"ABC">
,而当没有值时,就回来一个nil
。
我们再来看下面这段代码输出什么
let str: String? = "abc"
let upperStr = str?.uppercased().lowercased() // Optional<"abc">
相同的可选链关于下标和函数调用也适用
var closure: ((Int) -> ())?
closure?(1) // closure为nil不履行
let dict = ["one": 1, "two": 2]
dict?["one"] // Optional(1)
dict?["three"] // nil
??运算符
(a ?? b)
将对可选类型a
进行空判别,假设a
包括一个值就进行解包,否则就回来一个默许值b
。
- 表达式
a
有必要是Optional
类型。 - 默许值
b
的类型有必要要和a
存储值的类型保持一致。
山人解析可选类型
隐式解析可选类型是可选类型的一种,运用的进程中和非可选类型无异。它们之间仅有的区别是,隐式解析可选类型是你告知对 Swift 编译器,我在运行时拜访时,值不会为nil
。
var age: Int?
var age1: Int!//山人解析可选类型
age = nil
age1 = nil