一、枚举
1.1 定义如下:
enum Season {
case spring
case summer
case autumn
case winter
}
或者写成
enum Season {
case spring, summer, autumn, winter
}
var current = Season.spring
current = .summer
print(current) // summer
1.2 枚举关联值(associate value)
就是在枚举的声明中,关联某一个类型,配合switch使用枚举时,就可以从枚举中解析出关联值。
enum Direction {
case angle(Float)
case name(String)
}
var dir = Direction.angle(45)
dir = .name("东北方向")
// 从枚举中取关联值
switch dir {
case .angle(let pt):
print("当前方向角度:(pt)")
case .name(let name):
print("当前方向是:(name)")
}
1.3 枚举的原始值(raw value)
指的是在定义枚举的时候,可以给enum指定一个类型,然后可以给case中的枚举成员指定一个该类型的默认值,这个默认值就是枚举成员的关联值。
enum Direction : String { // 定义原始值的类型
case east = "东" // "东"就是原始值,依此类推
case south = "南"
case west = "西"
case north = "北"
}
var dir = Direction.east
print(dir) // east
print(dir.rawValue) // 东
print(Direction.east.rawValue) // 东
// 有一种直接创建枚举变量的方式
let dir = Direction(rawValue: "南")
// 如果不包括在枚举成员的所有原始值中,那么此时的dir就是一个Direction?可选类型
// 可能是nil,列如: Direction(rawValue: "南北")
// 可选类型在后面会写到
还有隐式原始值的概念,如下:
【如果枚举的原始值类型为Int、String,那么Swift会给枚举成员分配原始值。】
enum Direction : String {
case east, south, west, north
}
// 那么枚举成员的原始值就是对应的成员名称字符串
print(Direction.east) // east
print(Direction.north.rawValue) // east
enum Direction: Int {
case east, south, west, north
}
print(Direction.east.rawValue) // 0,后面成员的原始值依次加1
// 如果给case其中的某几个case赋原始值,那么没赋原始值的枚举成员,
// 它的原始值是紧靠它的前一个枚举成员原始值加1
// 列如
enum Direction: Int {
case east = 1, south, west = 8, winter
}
print(Direction.east.rawValue) // 1
print(Direction.south.rawValue) // 2
print(Direction.west.rawValue) // 8
print(Direction.north.rawValue) // 9
1.4 枚举的递归(recursive enumeration)
也就是指枚举的case枚举成员,如果有关联值,且其关联值类型就是当前枚举,这种情况就是递归枚举。
递归枚举在定义时必定要在enum前加indirect修饰,或者在递归的枚举成员case前加indirect修饰。
indirect enum Direction {
case angle(Float)
case twoDirection(Direction, Direction)
}
// 或者
enum Direction {
case angle(Float)
indirect case twoDirection(Direction, Direction)
}
二、MemoryLayout获取数据类型占用的内存大小
如果在Objective-C中,使用的是sizeof来获取类型占用的内存大小,Swift中使用的是MemoryLayout。
2.1 具体使用
MemoryLayout<Int>.size // 64位系统输出8,32位系统输出4
// 针对变量获取内存大小
let num = 8;
MemoryLayout.size(ofValue:num) // 8
2.2 其他使用
`MemoryLayout<Int>.stride`和`MemoryLayout<Int>.alignment`
- stride 是指系统分配的内存大小
- alignment是内存对齐的字节数
- size是指在分配的内存中实际占用了多少字节
// 和上面一样,都可以获取类型或者变量的类型的内存大小
MemoryLayout<Int>.stride
MemoryLayout<Int>.alignment
let num = 8;
MemoryLayout.stride(ofValue:num)
MemoryLayout.alignment(ofValue:num)
2.3 关联值和原始值在enum中的内存存储
- 关联值存储在枚举变量类型中,所以需要能够存储所有大小的关联值的类型,也就是说,枚举类型的内存大小是所有关联值类型内存大小的和。
举例如下:
enum Direction {
case ange(Int, Int)
case name // 这个地方如果没有指定关联类型,那么使用1个字节就可以存储
}
// 那么会是以下输出
print(MemoryLayout<Direction>.alignment) // 8
print(MemoryLayout<Direction>.size) // 17 解释:2 * 8 + 1 = 17
print(MemoryLayout<Direction>.stride) // 24 解释:,内存对齐是8,所以取8的倍数24
- 原始值,比较特殊,不会占用到枚举类型的内存中去,具体的原始值存储在其他地方。
enum Direction : String {
case east, south, west, north
}
enum Direction: Int {
case east = 1, south, west, north
}
// 上面2种情况,枚举类型占用的内存都是1个字节。
// 由于只用一个字节就可以表明出不同的枚举。
// 可以想象使用以下方式,表明上面的默认1的原始值写法,直接返回某个具体的值就可以,没有必要存储原始值的类型。
enum Direction: Int {
case east, south, west, north
func rawValue() -> Int {
if self == 0 return 1
if self == 1 return 2
...
}
}
三、可选项(optional)
也就是常说的可选类型,指一个变量的值可以是nil,或者是有确定的值。
3.1 定义
通过在类型名称后加一个?号,表明这个变量是可选类型
var name: String? = nil // 等同于 var name: String?,由于可选类型未赋值时,默认就是nil
name = "小明"
// 输出的是Optional包裹的值
print(name) // Optional("小明")
3.2 强制解包 & 可选项绑定
切记,对值为nil的可选变量强制解包,会runtime error
使用可选项时,需要判断是否为nil,或者复制给临时变量。这种方式叫可选项绑定。
var name: String? = "小明"
print(name!) // "小明" 强制解包出来的就是直接值,不会有Optional包裹
let name1: String = name!
// 使用可选项时,需要进行判断是否为nil,或者进行赋值
// 这就叫做可选项绑定
if name != nil {
// do something with name!
}
// 或者
if let sureName = name { // sureName的作用域仅在这个大括号内
// do something with sureName
}
这里还有一个概念,叫隐式解包,也就是直接使用感叹号!,例子如下:
var a: Int! = 1 // 这个叫隐式解包的可选项
// 这种方式可以给a置为nil,当然在使用之前必须赋值非nil,否则解包crash
a = nil
// 这种有时候用在跟assert的情况
3.3 空合运算符??(nil-coalescing operator)
指的是,用在可选类型变量后面,加一个??,紧跟一个同类型的变量,表明如果??前的可选项的值为nil,那么使用??后面的变量,否则使用??之前的变量。
let a: String?
let b = a ?? "未知名字" // 由于b的值为"未知名字",所以b的类型不是可选项类型
print("(a)") // nil
print("(b)") // "未知名字"
let a: String?
let b: String? = "1"
let c = a ?? b // 此时的c是Optional("1")
let num: Int? = 1
let num1: Int = 2
let result = num ?? num1 // 此时的result是Int,非可选项
小结一下:
- ??前的变量必须是可选项;
- 如果??前后的2个变量都是可选类型,不管这2个可选类型是否有值,那么被赋值变量也是可选项;
- 如果??前的可选项有值,??后的变量为非可选项,那么返回左边变量的时候就会对左边的可选项进行强制解包;
总之一句话,对于??这样的空合运算符,左侧的被赋值变量的类型,取决于??后面的变量类型。
另外,空合运算符可以联合使用,如下:
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 // 那么c是Int,非可选项
// 这种连续使用,左侧的c取决于最右侧的最后那个变量或者值
特别注意的地方:
- 如果从字典中通过key取值,被赋值的变量是可选项(如果没有对应的key,那么返回的是nil)
- 如果从数组中通过index取值,被赋值的变量是非可选项(如果有越界,程序直接crash)
let dic = ["name": "小明", "age": 1]
let name = dic["name"] // Optional("小明")
let gender = dic["gender"] // nil
let arr = [1, 2, 3]
let a = arr[0] // 1
let b = arr[3] // crash
3.4 guard
和if正好相反,if是true才去执行大括号内的语句,guard是false才去执行紧接着的大括号内语句。
在guard的else语句中,必定要写退出当前作用域(return、throw error、break、continue等),否则会报错
if condition {
// 如果condition为true,进入这个大括号
}
guard condition else {
// 如果condition为false,进入这个大括号
return
}
// 如果condition为true,会执行下面的代码
var name: String?
...
guard let sureName = name else {
print("name 不存在")
return
}
print("name is: (name)")
3.5 多重可选项
指大的是使用2个??来声明可选项
let a: Int? = 1
let b: Int?? = a
let c: Int?? = 1
// 在这种情况下,a 和 c 是等价的
print(b == c) // true
let a: Int? = nil
let b: Int?? = a
let c: Int?? = nil // 表明c中存储的是nil,而不是Optional(nil)
// 在这种情况下,a 和 c 是不等价的
print(b == 3) // false
千万留心下面的这种情况:
let a: Int? = nil
let b: Int?? = a
let c: Int?? = nil
print((b ?? 1) ?? 2) // 2,解释:第一b中存储了a这样一个可选项,那么b第一次解包出来的是a,然后再解1次包是nil
print((c ?? 1) ?? 2) // 1,解释:第一c中存储了nil,那么第一次解包出来就是nil,所以将1返回。
3.6 对多重可选项,可以使用断点调试来探究其构成
断点使用llvm调试命令:frame variable -R xxx,xxx是变量
(lldb) frame variable -R xxx
// 或者简写
(lldb) fr v -R xxx

可以看到每一个可选变量,它们的stack frame结构。
双重可选变量内部是包裹的一个可选变量。

这是《Swift知识一览》系列的第二篇文章,若感兴趣,请关注后续发表的文章。
更多信息请前往:新卫网络-首页 或者关注“新卫网络科技”公众号















暂无评论内容