一、枚举
1、枚举的基本用法
通过enum关键字来声明
enum Season {
case spring
case summer
case autumn
case winter
}
等价于:
enum Season{
case spring,summer,autumn,winter
}
在oc中默认只接受整数类型
//以下的值分别是0 1 2
typedef enum : NSUInteger {
enum_A,
enum_B,
enum_C,
} MyEnum;
而在swift中更加灵活:
- 原始值可以是字符串、字符、整数值、浮点值
enum Color:String {
case red = "Red"
case amber = "Amber"
case green = "Green"
}
enum MyEnum:Double {
case enum_A = 10.0
case enum_B = 20.0
case enum_C = 30.0
}
- 不需要给枚举中的每个成员都提供值 Swift隐式RawValue分配,它建立在swift的类型推断机制上,即使你没有给每个枚举成员提供值,它也会自动提供符合指定类型的值
//指定为Int类型
//前面是 0 1 2 3 从fri开始就是 10 11 12
enum EnumWeek:Int{
case mon, tue, wed, thu, fri = 10, sat, sun
}
print(EnumWeek.mon)
print(EnumWeek.mon.rawValue)
print(EnumWeek.fri.rawValue)
//指定为String类型
enum EnumWeek:String{
case mon, tue, wed, thu, fri = "Hello", sat, sun
}
print(EnumWeek.mon)
print(EnumWeek.mon.rawValue)//原始值
print(EnumWeek.fri.rawValue)//原始值
看一下打印结果:
当指定为String类型的时候,rowValue的值跟枚举成员的字符串一样,来看一下SIL,找找为什么:
enum EnumWeek:String{
case mon, tue, wed, thu, fri = "Hello", sat, sun
}
var x = EnumWeek.mon.rawValue
从这个结构体可以看出,我们调用rawValue的时候,其实是调用的get方法,那就找一下rawValue的get方法:
mon被作为一个参数传入了get方法
通过switch模式匹配,最终返回utf8编码的“mon”,那么这个“mon”是存在于哪里呢?我们看一下mach-o:
是存在于
__Text,__cstring这个Section中。
1.2 枚举值与原始值
enum EnumWeek:String{
case mon, tue, wed, thu, fri = "Hello", sat, sun
}
print(EnumWeek.mon)//枚举值
print(EnumWeek.mon.rawValue)//原始值
虽然它俩打印出来一样,但其实类型是不一样,枚举值它是
EnumWeek类型,原始值是String类型
1.3 枚举的可失败初始化器
当我们给到一个原始值rawValue,希望得到其对应的枚举值case,就可以使用可失败初始化器
但给了一个枚举类型中不存在的
原始值rawValue的时候,会返回nil
2、枚举的关联值
简单的枚举只有一个定义的类型,而枚举关联值可以给每一个枚举值都关联一个类型,但它就没有原始值
enum Shap{
case circle(radios:Double) //关联了double
case rectangle(width:Double, height:Double)//关联了double,double
}
var circle = Shap.circle(radios: 10)
print(circle)
3、枚举模式匹配
在Swift中使用switch进行模式匹配的时候,有几个点要注意:
- 要匹配所有的case【oc中可以不用】,否则会报错
不想匹配所有case的话,可以先初始化然后增加
default关键字
匹配【枚举关联值】 写法一:
写法二: var修饰的参数可以修改
4、枚举值的内存大小
枚举占用的内存大小,这里我们区分几种不同的情况
No-payload enums无关联值的枚举(无负载)Single-payload enums只有一个关联值的枚举(一个负载)Mutil-payload enums有多个关联值的枚举(多个负载)
4.1 无关联值的枚举 占用的内存大小
enum Season {
case spring
case summer
case autumn
case winter
}
var a = Season.spring
var b = Season.summer
var c = Season.autumn
var e = Season.winter
print(MemoryLayout<Season>.size) // 内存大小 1
print(MemoryLayout<Season>.stride) // 1
print(MemoryLayout<Season>.alignment) // 1
打断点,利用以下两个lldb命令打印,观察内存占用情况:
po withUnsafePointer(to: &a, {print($0)})
memory read 0x0000000100008068
可以看到,每个枚举值占用一个【十六进制中的一位】
对于无关联值的枚举来说,它有一个隐式值硬编码入mach-o,在内存中只需要存储枚举值,默认是以
UInt8存储的,也就是1字节
- UInt8 = 2^8 = 256 所以对于无关联值的枚举来说,一个字节最多能存256个枚举值,超过256,系统就升级成UInt16 -> UInt32 -> UInt64
4.1 只有一个关联值的枚举 占用的内存大小
4.1.1 当关联值是BOOL类型时:
看一下这个例子:
enum CTEnum {
case enum_One(Bool)
case enum_Two
case enum_Three
case enum_Four
}
print(MemoryLayout<CTEnum>.size)
Bool值虽然需要占用一个字节的内存,但实际存储的是0x00,或者0x01,也就是这一个字节中BOOL值它没有完全利用,系统把剩余的未利用的空间,来存储枚举中其他枚举值,也就是余下的7位,此时这个枚举最多只能有
2^7 = 128 个枚举值。如果不够,会申请更多内存来存储。
4.1.2 当关联值是Int类型时:
把上面bool类型换成Int类型试一下:
enum CTEnum {
case enum_One(Int)
case enum_Two
case enum_Three
case enum_Four
}
print(MemoryLayout<CTEnum>.size) //9
print(MemoryLayout<CTEnum>.stride) // 16
print(MemoryLayout<CTEnum>.alignment) // 8
Int本身已经占了8位(1个字节),它无法向Bool一样有剩余的空间来存储其他枚举值
所以再需要一个字节来存储其他枚举值,就有了 8(Int) + 1 = 9
总结一下:只有一个关联值的枚举,它占用的内存大小与它关联的那个类型有关,有没有剩余空间可以存储其他枚举值
4.2 有多个关联值的枚举 占用的内存大小
先测一下几个不同的枚举占用内存大小
//4个bool关联值
enum Season1 {
case spring(Bool)
case summer(Bool)
case autumn(Bool)
case winter(Bool)
}
//4个Int关联值
enum Season2 {
case spring(Int)
case summer(Int)
case autumn(Int)
case winter(Int)
}
//一个Bool 一个Int 两个无关联值的枚举值
enum Season3 {
case spring(Bool)
case summer(Int)
case autumn
case winter
}
//一个 4*int关联值 三个无关联值的枚举值
enum Season4 {
case spring(Int, Int, Int, Int)
case summer
case autumn
case winter
}
print(MemoryLayout<Season1>.size) // 1
print(MemoryLayout<Season2>.size) // 9
print(MemoryLayout<Season3>.size) // 9
print(MemoryLayout<Season4>.size) // 33
结论:
- 有多个关联值的时候,如果关联值大小加起来都小于一字节,那么枚举的大小就是1字节。
- 如果关联值大小大于1字节,枚举的大小为:最大关联值的大小 + 1。
4.3 特殊情况
enum Season {
case season
}
print(MemoryLayout<Season>.size) // 0
当枚举中只有一个枚举值的时候,不需要任何东西来区分case,所以大小是0
4.4 关联值内存分布
当枚举中只有一个关联值的时候,内存的排布式连续的
有两个以上的关联值的时候,内存则不是连续排布
5、indirect关键字
有的时候我们想把枚举的关联值定义成这个枚举本身的类型(递归),像这样:
enum BinaryTree<T>{
case empty
case node(left:BinaryTree, value: T , right:BinaryTree)
}
默认情况下是不允许的,需要添加
indirect关键字
5.1 在枚举类型前面增加indirect关键字
indirect enum BinaryTree<T>{
case empty
case node(left:BinaryTree, value: T , right:BinaryTree)
}
默认情况夏枚举是值类型,是分配在栈空间的,而增加了
indirect关键字之后,这个枚举里有关联类型的枚举值会被分配在堆空间上。
下面通过几个角度来证明:
indirect enum BinaryTree<T>{
case empty
case node(left:BinaryTree, value: T , right:BinaryTree)
}
var node = BinaryTree<Int>.node(left: BinaryTree<Int>.empty, value: 10, right: BinaryTree<Int>.empty)
print("end")
1、从LLDB的角度:
2、从Mach-o内存分配的角度:
3、从SIL的角度:
在main函数中,调用了
alloc_box,从sil文档可以查阅到:alloc_box的作用就是在堆上分配空间。本质上就是调用swift_allocObject【SIL文档】
4、从汇编的角度:
5、枚举中无关联类型
5.2 在case前面增加indirect关键字
如果只有某个枚举值增加了indirect关键字,那么就只有这个枚举值是引用类型,只有这个枚举值分配到堆空间上。
enum BinaryTree<T>{
case empty
indirect case node(left:BinaryTree, value: T , right:BinaryTree)
}
var node = BinaryTree<Int>.node(left: BinaryTree<Int>.empty, value: 10, right: BinaryTree<Int>.empty)
var empty = BinaryTree<Int>.empty
print("end")
总结一下:
- 1、默认情况下枚举值是值类型,是分配在栈空间的,而增加了
indirect关键字之后,这个枚举里有关联类型的枚举值会被分配在堆空间上。- 2、如果只有某个枚举值增加了
indirect关键字,那么就只有这个枚举值是引用类型,只有这个枚举值分配到堆空间上。- 3、无关联类型的枚举值不能使用
indirect关键字。
二、Optional可选值
1、可选值
var age: Int?
//等价于
var age:Optional<Int>
从源码的角度分析一下Optional(Optional.swift):
Optional本质是一个枚举,有两个参数,none和some,some存着我们对应的类型
2、可选值绑定(解包)
由于Optionl是一个枚举,那我们就可以对一个optional的变量进行模式匹配,以保证我们使用这个变量时确保它有值。
var age:Int? = 10;
switch age {
case .some(let value):
print(value)
case .none:
print("not exits")
}
2.1 可选值绑定if let
但这种写法太过麻烦,可以采取if let可选值绑定的方法
Optionl可选值可以想象成一个盒子,if let相当于把盒子里的东西取出来,赋值给let,这个过程称为可选值绑定或解包,如果盒子里没有东西,那就是nil,则if let判断不成立。
var age:Int? = 10
if let value = age {
print(value)
}
2.2 guard let守护
还有一种方法是guard let守护
- 这个let一定有值,否则return
- 通常判断是否有值后,在内部写具体逻辑实现
func submit() {
let age:String? = "18"
guard let value = age else{
print("age为空")
return
}
print("age判断通过")
}
submit()
guard let内部还可以增加条件,比如:
三、??运算符(空合并运算符)
(a ?? b)
var age:Double?
var x = age ?? 10.11
print(x)
将可选类型a进行空判断,如果a有值,就进行解包,否则就返回默认值b
-
表达式a必须是
optional -
默认值b类型必须和a存储值的类型保持一致
看看源码:
其实就是重载运算符
四、运算符重载
4.1 运算符重载
Swift提供了一些方法,让我们也能自己来实现运算符重载
struct Vector{
let x:Int
let y:Int
}
extension Vector{
static func + (firstVector:Vector, secondVector:Vector) -> Vector{
return Vector(x: firstVector.x + secondVector.x, y: firstVector.y + secondVector.y)
}
static prefix func - (vector:Vector) -> Vector {
return Vector(x: -vector.x, y: -vector.y)
}
static func - (firstVector:Vector, secondVector:Vector) -> Vector{
return firstVector + (-secondVector)
}
}
var x = Vector(x: 10, y: 20)
var y = Vector(x: 20, y: 30)
var z = x + y
print(z) //Vector(x: 30, y: 50)
var z1 = y - x
print(z1) //Vector(x: 10, y: 10)
var z2 = -x
print(z2) //Vector(x: -10, y: -20)
4.2 自定义运算符
4.2.1 自定义运算符
我们可以自己创造一个符号,然后给它一个运算的实现
例子:
infix operator ><
关键字
infix是定义中缀运算的,还有prefix前缀运算符,postfix后缀运算符
4.2.1 指定运算符【优先级组】
优先级组
- 同类型的操作符组成一个优先级组(precedence group)
- 组与组之间、组内操作符成员 的运算优先级 从上到下表示优先级从高到低
例子:以下是BitwiseShiftPrecedence组 和 MultiplicationPrecedence组
在自定义中缀操作符的时候,可以指定它属于哪个【优先级组】的。当不指定时,模式是DefaultPrecedence也就是BitwiseShiftPrecedence,默认的优先级挺高的,比加减乘除高。
infix operator ><:MultiplicationPrecedence
这个时候,><运算符的优先级就跟MultiplicationPrecedence这个组的优先级一样
4.2.2 自定义优先级组
我们可以自己定义一个优先级组,然后再自定义的运算符中指定到这个优先级组
precedencegroup MyPrecedenceGroup {
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
associativity: left
assignment: false
}
infix operator ><: MyPrecedenceGroup
higherThan:比某个优先级组的优先级高。
lowerThan:比某个优先级组的优先级底。
associativity:运算顺序,left从左到右 right从右到左 none不许多个同样的运算符出现。
assignment:是否允许可选值使用运算符,TRUE的时候同意,且当可选值为nil的时候,不执行运算。
associativity:运算顺序:
assignment:是否允许可选值使用运算符
五、隐式解析可选类型
解析可选类型 可以是显式的,也可以说是手动解包:
var age:Int? //可选类型
//if let解包
if let value = age {
print(value)
}
//guard let守护
func MyFunc(){
guard let value = age else {
print("is nil")
return
}
print(value)
}
也可以是隐式的:
var age:Int!
print(age)
隐式解析可选类型相当于你告诉对 Swift 编译器,我在运行时访问时,一定有值。那么编译器便不会去检查它,但如果不讲武德实际上里面是nil,那么就会崩溃。