swift枚举,可选型

  • 内联函数:
  1. 如果开启了编译器优化,编译器会自动把某些函数变成内联函数:直接将函数展开成函数体内容。
  2. 不会被内联的函数:
  • 函数体过长,会使得编译代码过分臃肿。
  • 包含递归调用,展开后无法返回。
  • 包含动态派发, 无法在编译时展开。

使函数不会被内联:

  1. @inline(never) func test{} //此类函数永远不会被内联。
  2. @inline(_always) func test{} //过长的函数体也会被内联。

一,枚举:

1.基本用法:

enum test{
	case A,B,C,D
}
var enumTest = test.A //此时enumTest已经是枚举类型。
enumTest = .D //可以直接用下级成员修改
print(enumTest) //D
  • 枚举一般用在一个变量的取值在固定的几种类型中。
  • 关联值:将枚举的成员和其他类型的关联存储在一起。比如enum abc就设立了一个关联值,关联值的值直接存入内存。
  • 原始值:枚举成员可以使用相同类型的默认值预先关联,默认值就被称为原始值。比如enum abc:Int就设立了一个原始值,enum中存入的是原始值的引用地址。在枚举值后面加上rawValue可访问原始值。
  • 隐藏原始值:如果枚举类型的值类型是Int,String,会自动分配他的值为其原始值。
  • 枚举递归:indirect enum test{}来创建枚举递归,可以在其中使用自己的类型。
  • MemoryLayout:可以用MemoryLayout获取数据类型占用的内存大小。约等于sizeof。
//可接受范型
MemoryLayout<Int>.size //8,因为系统运作在64位电脑上,此时的Int == Int64
MemoryLayout<Int>.stride //8
MemoryLayout<Int>.alignment //8

//对对象使用
var age:Int = 10
MemoryLayout.size(ofValue:age) // 8
MemoryLayout.stride(ofValue:age) // 8
MemoryLayout.stride(ofValue:age) // 8

//在枚举中:
enum password{
	case number(Int,Int,Int,Int) //number的类型是由4个Int类型构成的。
	case other
}
//该枚举属于关联值,关联值传入枚举的值直接存储在内存中.
//关联值可以在之后的定义之中传入新的值,比如定义一个password.number(1,2,3,4)或者password.number(5,6,7,8).
//所以每一个枚举变量都需要空间去存储自己的值。

MemoryLayout<password>.size // 33,实际用到的空间大小,其中32个放number,还有一个字节放other
MemoryLayout<password>.stride // 40,分配占用的空间大小,因为只多出来一个字节,所以对齐补到8字节,总共40字节。
MemoryLayout<password>.alignment // 8,对齐参数,对齐参数是变化的,主要是以number中的Int为主,因为是64位系统,Int占8字节。


-------------------------------------------------------
//不懂的奇怪地方,其中的password.number占16字节
//这个设定是错误的,因为如果用到number其实是一个函数赋值的
//但是此时没有赋值
//var a = password.number
//print(a) 
//它的输出是打印出:(Function)
//所以password.number所占的内存不是真实占的
//可能属于一个函数体占的内存,有待研究
MemoryLayout.size(ofValue:password.number) //16
MemoryLayout.stride(ofValue:password.number) // 16
MemoryLayout.stride(ofValue:password.number) // 16
-------------------------------------------------------

enum Season : Int{
	case a = 1,b = 2,c = 3,d = 4
}
//该枚举值属于原始值,其值是固定的,在设立之初就绑定好的。
//原始值无法再重新传入值。
//原始值不存入内存中,rawValue可能是通过代码判断传出的
//直接访问a的时候,其实只是访问了占一个内存的成员变量的序列,所以在MemoryLayout下是1字节。
//在使用a.rawValue时候getter它真实的值,此时是Int,返回8个字节。

var s = Season.a // a
var s1 = Season.a.rawValue // 1

MemoryLayout.size(ofValue:Season.a)//1,存储的是一个原始值的成员序列的大小
MemoryLayout.size(ofValue:Season.a.rawValue)//8,通过rawValue访问原始值

MemoryLayout<Season>.size // 1
MemoryLayout<Season>.stride // 1
MemoryLayout<Season>.alignment // 1

//再创一个类似的关联值。
enum Season2{
    case a(Int)
}
MemoryLayout<Season2>.size // 8
MemoryLayout<Season2>.stride // 8
MemoryLayout<Season2>.alignment // 8

枚举的内存

  • 枚举的内存申请经过下列实验,猜测是在代码执行时候才分配的。
    1.原始值
  • 原始值存储的是成员值的序列,不使用rawValue的时候不占用枚举的内存空间。
enum testEunm:Int{
    case test1 = 1,test2 = 2,test3 = 3
}
var t = testEunm.test1 // 查看t内存数值:00
t = .test2 // 查看t内存数值:01
t = .test3 // 查看t内存数值:02
//t的内存存的是testEnum中的值,是以0开头顺序下去的一个初始值的。
//若是原始值,test1 = 1,test2 = 2,test3 = 3这样的,值也不是存储在枚举中,而是可以通过枚举的rawValue获得他的原始值。
//原始值不影响枚举变量的存储。
  • 是否使用到rawValue对于内存空间的影响:
enum Season:Int{
    case a = 1
    case b = 2
    case c = 3
}
//在访问a,b,c且访问其rawValue的情况下:
//其内存为:
//00 00 00 00 00 00 00 00  ->a的成员序号
//01 00 00 00 00 00 00 00  ->a的rawValue
//01 00 00 00 00 00 00 00  ->b的成员序号
//02 00 00 00 00 00 00 00  ->b的rawValue
//02 00 00 00 00 00 00 00  ->c的成员序号
//03 00 00 00 00 00 00 00  ->c的rawValue

//在不访问rawValue的情况下:
//其内存为:00 01 02 00 00 00 00 00 -> 只存储了成员序号

//在只访问a且不访问其rawValue的情况下:
//内存为:00 00 00 00 00 00 00 00

//所以枚举变量的内存分配,是在执行代码时候分配的,用到就有,没用到就没有

2.关联值

  • 关联值本质就是将成员的序列值和其他类型的值关联在一起存储。
  • 在枚举变量中,有一个字节存储成员值,像数组的索引,从00到FF(0…256)是成员的排序。用来说明这是第几个case。
  • 由N个字节来存储关联值,N个字节来源于枚举中所占内存最大的那一个成员。
  • 证明过程如下:
enum testEnum{
	case test1(Int,Int,Int)
	case test2(Int,Int)
	case test3(Int)
	case test4(Bool)
	case test5
}

print(MemoryLayout<testEnum>.size) // 25,实际
print(MemoryLayout<testEnum>.stride) // 32,分配
print(MemoryLayout<testEnum>.alignment) // 8,对齐参数

var t = testEnum.test1(1,2,3)
//首次赋值中的内存是:
//01 00 00 00 00 00 00 00 ->存储了数字1
//02 00 00 00 00 00 00 00 ->存储了数字2
//03 00 00 00 00 00 00 00 ->存储了数字3
//00                      ->该字节存储成员值,从0开始
//00 00 00 00 00 00 00	  ->补齐参数所没用到的7字节

t = .test2(4,5)
//第二次赋值中的内存是:
//04 00 00 00 00 00 00 00 ->存储了数字4
//05 00 00 00 00 00 00 00 ->存储了数字5
//00 00 00 00 00 00 00 00 ->现在只有两个数字,所以为空
//01                      ->存储成员值,使用的是第二个成员,所以为1
//00 00 00 00 00 00 00    ->补齐参数所没用到的7字节

t = .test3(6)
//第二次赋值中的内存是:
//06 00 00 00 00 00 00 00 ->存储了数字6
//00 00 00 00 00 00 00 00 ->现在只有一个数字,所以为空
//00 00 00 00 00 00 00 00 ->现在只有一个数字,所以为空
//02                      ->存储成员值,使用的是第三个成员,所以为2
//00 00 00 00 00 00 00    ->补齐参数所没用到的7字节

t = .test4(true)
//第三次赋值中的内存是:
//01 00 00 00 00 00 00 00 ->存储了Bool值true
//00 00 00 00 00 00 00 00 ->现在只有一个Bool值,所以为空
//00 00 00 00 00 00 00 00 ->现在只有一个Bool值,所以为空
//03                      ->存储成员值,使用的是第四个成员,所以为3
//00 00 00 00 00 00 00    ->补齐参数所没用到的7字节

t = .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                      ->存储成员值,使用的是第五个成员,所以为4
//00 00 00 00 00 00 00    ->补齐参数所没用到的7字节
  1. 如果枚举中只有一个值
enum TestEnum{
	case test
}

print(MemoryLayout<testEnum>.size) // 0  -> 因为枚举中只有一个值,不需要去分辨成员索引。
print(MemoryLayout<testEnum>.stride) // 1
print(MemoryLayout<testEnum>.alignment) // 1

二.可选项(Optional)

  1. 可选项,一般也叫可选类型,它允许将值设置成nil。
  2. 在类型名称后加一个问号 ? 来定义一个可选类型。
  3. 强制解包:使用感叹号 ! 来强制将可选项中包装的数值取出来使用。无法对为nil的可选项强制解包就会报错,强制解包有较大的危险性。
  4. 可选项绑定:用来判断可选项是否包含值,如果包含就自动解包,把可选项的值赋予一个临时的常量或者变量,并返回true,否则返回false。就是常用的 if let,可选项绑定使用的变量只存在于这个判断的大括号中。
  5. 在可选项绑定的多重判断中,不能使用&&或者 || 的与或标示,需要使用,(逗号)来隔开多个可选项绑定的条件。
  6. while let 也是可以使用可选项绑定的,一般用来取数组或者一些需要一个一个判断可选项的数组。
  7. 空合运算符:a ?? b ,在空合运算符中,a必须是可选项,b可以是可选项或不是可选项,但是a和b的存储类型必须相同,比如a是Int?,那么b必须是Int?或者Int。如果a为nil则返回b,如果a不为nil则返回a,如果b不是可选项则返回a时会自动解包。
  8. 所以在空合中,返回的类型取决于b,在swift对??的定义中可以查看到其原定义代码。
  9. 多个空合连用中,从左到右运算,返回的最终类型取决于最右。且除了最右以外参与多个空合运算中的其他变量,都需要是可选项。
  10. 字典根据key取出的value是可选项。
  11. guard语句:
guard 条件 else{
	// do something
	退出当前作用域 //使用return,break,contine,throw error
}
  • 当guard语句为false时,就会执行后接大括号中的代码。
  • 当guard语句为ture时,会跳过后接大括号。
  • 当使用guard语句进行可选项绑定时(guard let),绑定的常量可以在外层作用域使用。
  1. 隐式解包:在某些情况下,可选项一旦被设定值之后,就会一直拥有值。
  • 可以去掉检查步骤,因为可以确定在每次访问的时候都有值。
  • 可以在类型之后加 ! 定义一个隐式解包的可选项。
  • 一个值,如果是Int?或者Int!都是可选项,强制解包则是在这些值被赋值的时候加上!
  • 在使用隐式解包时候,相当于希望获得一个非nil的东西,但是还是可能会获得一个nil,如果获得一个nil的赋值,则会报错。
  • 可以少用一些。
  1. 在字符串插值中使用可选项中会用警告:
var age:Int? = 10
print(age) // 输出:Optional(10),但是会有警告。
print("my age is \(age)") // 输出:my age is Optional(10),也会有警告。
//可以强制解包,或者用print("my age is \(age ?? 0)")
  1. 多重可选项,最多有两层,就是Int??之类的,且已经??的项就不能再被设立为可选项了,比如var a:Int?? = 10,则不可以设立一个b:Int? = a或者b:Int?? = a了
var num1:Int? = 10
var num2:Int?? = num1 //Int??中装了Int?,在Int?中装了真实的数据
var num3:Int?? = 10 //num3和num2是等价的,
print(num1 == num2 && num2 == num3 && num1 == num3) // true

var num1:Int? = nil
var num2:Int?? = num1 //Int??中装了Int?,在Int?中装了nil
var num3:Int?? = nil //num3和num2不等价的,Int??此时直接装了nil
print(num1 == num2) //true,因为num2是被num1直接赋值的,所以还是等价。
print(num1 == num3) //false
print(num2 == num3) //false


(num2 ?? 1) ?? 2 // 2,开局num2不为nil(因为其中还装了num1),所以先返回num2,且因为与其空合的是1为Int,是非可选项,所以num2被解包,解包后发现num2其实是nil,所以返回2
(num3 ?? 1) ?? 2 // 1,开局直接知道num3是nil,所以直接返回1

var num4 = num2 ?? 1 //nil,实际第一步发生的就是这样的。

在命令行中可以查看:
如果是some,则后面的大括号有意义,如果是none,则后面的大括号不用看,没有意义。

(lldb) fr v -R num1
(Swift.Optional<Swift.Int>) num1 = none {
  some = {
    _value = 0
  }
}
(lldb) fr v -R num2
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
  some = none {
    some = {
      _value = 0
    }
  }
}
(lldb) fr v -R num3
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = none {
  some = some {
    some = {
      _value = 0
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值