Swift —— Enum & optional

Swift —— Enum & optional,第1张

Swift —— Enum & optional 1. 枚举的基本用法2. 关联值3. 模式匹配4. 枚举的大小5. indirect6. Optional6.1 Optional6.2 if let6.3 guard let6.4 可选链6.5 ?? 运算符 (空合并运算符)6.5 运算符重载6.6 自定义运算符6.7 隐士解析可选类型

1. 枚举的基本用法

swift 中通过 enum 关键字来声明一个枚举

enum LGEnum{
    case test_one
    case test_two
    case test_three
}

在 C 或者 OC中默认受整数支持,也就意味着下面的例子中: 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 LGEnum: Double {
    case a = 10.0
    case b = 20.0
    case c = 30.0
    case d = 40.0
}

隐士 RawValue 分配 是建立在 Swift 的类型推断机制上的。例如下面的代码:

enum DayOfWeek: Int {
mon, tue, wed, thu, fri = 10, sat, sun
}

这样的代码运行后,可以看到sat和sun系统就会进行推断,然后赋值为11,12,而前面的则依然是默认的raw值。

而string类型的话则依然是默认的和本身一样的raw值,

从SIL来看,这里有一个可失败的初始化器以及一个rawValue的get方法,那么访问这个rawValue也就是通过这个get方法。

尝试访问DayOfWeek.mon.rawValue

在SIL可以看到这里调用了rawValue的getter方法,并且把这个枚举值到一个方法里。

那么搜索到这个方法,看到这个方法根据传进来的值进行了模式匹配,那么如果是mon就会跳到bb1的代码块,在代码块里面就会将不同的字符串给到返回值。

enum里面有枚举值和原始值,这里虽然打印出来的值是一样的,但是他们类型并不一样。

这里可以看到,DayOfWeek.mon是DayOfWeek类型的,所以无法赋值给String类型的。

刚才看到SIL里面是一个可失败的初始化器,这个初始化器在使用DayOfWeek.mon.rawValue的时候不会被调用,而是在调用DayOfWeek.init(rawValue: )的时候被调用,这个方法是在给定一个原始值的时候,去找到枚举值,这个时候就会调用可失败的初始化器。这个初始化器可失败的原因是因为可能找不到匹配的枚举值,这个时候就需要返回nil。

看到SIL里面在构建字符串然后传进去参数。

接着看到DayOfWeek.init(rawValue:),StaticString说明在创建rawValue的时候我们的系统是自动创建了一个连续的内存空间来存储case与之匹配的字符串,mon传进来之后,就会拿传进来的moon和这些值对比,匹配的话就返回相应的枚举值。

2. 关联值

有时候想用枚举表达更复杂的案例的话,就可以使用关联值,比如形状。当给定关联值了之后,对与枚举成员变量来说就没有原始值了。

enum Shape{
    case circle(radious:Double)
    case rectangle(height: Int, width: Int)
} 

3. 模式匹配

模式匹配是通过switch来匹配当前的枚举值,从而执行想要执行的代码。

如果不想匹配所有的 case ,使用 defalut 关键字

如果我们要匹配关联值的话:

enum Shape{
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

let shape = Shape.circle(radious: 10.0)

switch shape{
case let .circle(radious):
    print("Circle radious:\(radious)")
case let .rectangle(width, height):
    print("rectangle width:\(width),height\(height)")
}


还可以这么写

枚举里面也是可以定义方法,属性,并且可以使用extension,也可以遵循协议,如果想要在方法里面改变自身属性,就需要加mutating关键字。

4. 枚举的大小

首先第一种就是No-payload enums,也就是没有关联值的情况。这里看到大小是1.对于隐士值来说,他其实已经硬编码进macho的字符串表了,我们需要存储的是枚举值 ,对于枚举值默认来说是以UInt8类型存储的,也就是默认1字节,最多存放256个。而当超过256个的时候,就会升级成UInt16.


Single-payload enums的内存布局, 字面意思就是有一个负载的 enum
比如下面这个例子

enum LGEnum{
    case test_one(Bool)
    case test_two
    case test_three
    case test_four
}

运行后看到大小是1。

而如果是Int,则是9.对于系统来说,并不是直接将关联值 加枚举值的1字节,而是取决于关联值的关联类型是否有额外的空间能够记录当前的case值。首先 Bool 类型是 1字节,也就是 UInt8 ,所以当 前能表达 256 个 case的情况,对于 布尔类型来说,只需要使用低位的 0, 1 这两种情况,其 他剩余的空间就可以用来表示没有负载的 case 值。对于 Int 类型的负载来说,其实系统是没有办法推算当前的负载所要使用的位数,也就意味着 当前 Int 类型的负载是没有额外的剩余空间的,这个时候我们就需要额外开辟内存空间来去存 储我们的 case 值,也就是8+1 =9字节。

上面说完了 Single-payload enums , 接下来我们说第三种情况 Mutil-payload enums , 有多个负载 的情况产生时,当前的 enum 是如何进行布局的呢?这里可以看到有两个 Bool 类型的负载,这个时候我们打印当前的 enum 大小 发现其大小仍然为 1,这个时候我们来看一下内存当中的存储情况,对于 bool 类型来说,我们存储的无非就是 0 或 1 ,只需要用到 1 位,所以剩余的 7 位这里我们都统称为 common spare bits,对于当前的 case 数量来说我们完全可以把所有的情况放到common spare bits中,所以这里我们只需要 1 字节就可以存储所有的内容了。所以这里就是最大的占用字节数,然后根据是否有common spare bits储存case来决定是否+1。


源码:

5. indirect

写一个二叉树枚举值,对于关联值来说,也是当前的枚举类型。那么对于枚举来说,枚举是值类型,值类型的大小在编译时就应该确定大小,但是下面的写法无法确定枚举的大小。

这个时候就需要给个indirect关键字,告诉系统将这个枚举分配到堆空间上。

这里打印出来查看node的内存空间,可以看到这里并不直接储存值,而是最终将其当作一个对象存储起来。所以使用indirect是将枚举类型在堆上分配内存空间。

看到这里SIL文件里面有调用alloc_box,这里实际上是调用swift_allocObject来分配内存空间,所以就是把node的值放到了堆空间上。

如果把indirect放在case前面那么就只有当前case使用引用类型,也就是在堆空间上分配内存空间存储case的值。而其他的则是值类型。

6. Optional 6.1 Optional

可选值可以这样定义:

class LGTeacher { 
var age: Int? 
}

这样age就是一个可选值,可选值还可以这样定义:

var age: Int? = var age: Optional

那对于 Optional 的本质是什么?我们直接跳转到源码,打开 Optional.swift 文件,可以看到Optional本质是enum。

既然 Optional 的本质是枚举,那么我们也可以仿照系统的实现制作一个自己的 Optional

enum MyOptional {
    case some(Value)
    case none
}

这个时候给定一个数组,我们想删除数组中所有的偶数.这个时候编译器就会检查我们当前的 value 会发现他的类型和系统编译器期望的类型不符,这 个时候我们就能使用 MyOptional 来限制语法的安全性。

这里可以通过 enum 的模式匹配来取出对应的值

如果我们把getOddValue的返回值更换一下,其实就和系统的 Optional 使用没有差别。

func getOddValue(_ value: Int) -> Int? {
    if value % 2 == 0 {
        return .some(value)
        
    }
    else{
        return .none
    }
}
6.2 if let

当然如果每一个可选值都用模式匹配的方式来获取值在代码书写上就比较繁琐,我们还可以使 用 if let 的方式来进行可选值绑定

6.3 guard let

除了使用 if let 来处理可选值之外,我们还可以使用 gurad let 来简化我们的代码。和 if let 刚好相反,guard let 一定有值,如果没有,直接返回。 通常判断是否有值之后,会做具体的逻辑实现,通常代码多
如果用 if let 凭空多了一层分支, guard let 是降低分支层次的办法

6.4 可选链

我们都知道再 OC 中我们给一个 nil 对象发送消息什么也不会发生, Swift 中我们是没有办 法向一个 nil 对象直接发送消息,但是借助可选链可以达到类似的效果。

同样的可选链对于下标和函数调用也适用

6.5 ?? 运算符 (空合并运算符)

( a ?? b ) 将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回 一个默认值 b .

表达式 a 必须是 Optional 类型默认值 b 的类型必须要和 a 存储值的类型保持一致

6.5 运算符重载

在源码中我们可以看到除了重载了 ?? 运算符, Optional 类型还重载了 == , ?= 等 等运算符,实际开发中我们可以通过重载运算符简化我们的表达式。

比如在开发中我们定义了一个二维向量,这个时候我们想对两个向量进行基本的 *** 作,那么我们
就可以通过重载运算符来达到我们的目的

struct Vector {
    let x: Int
    let y: Int
}
extension Vector {
    static func + (fistVector: Vector, secondVector: Vector) -> Vector {
        return Vector(x: fistVector.x + secondVector.x, y: fistVector.y + secondVector.y)
        
    }
    static prefix func - (vector: Vector) -> Vector {
        return Vector(x: -vector.x, y: -vector.y)
    }
    static func - (fistVector: Vector, secondVector: Vector) -> Vector {
        return fistVector + -secondVector
        
    }
    
}

这个时候就可以进行向量的加法运算。

而这里通过prefix添加了前置运算符,这样就可以就将向量按照方法中的代码改变。

6.6 自定义运算符

要创建 自定义运算符,先要指定运算符的优先级,然后指定associativity(结合性),最后实现方法。

6.7 隐士解析可选类型

隐式解析可选类型是可选类型的一种,使用的过程中和非可选类型无异。它们之间唯一 的区别是,隐式解析可选类型是你告诉对 Swift 编译器,我在运行时访问时,值不会 为 nil。
这个时候age不可以赋值为nil,但是age1可以。

这里需要注意的是,如果运行过程中使用age1的时候age1为nil,那么程序就会报错。

这是因为隐式解析可选类型在运行过程中相当于做了解包的 *** 作,所以age1为nil,那么程序就会报错。

IBOutlet类型是Xcode强制为可选类型的,因为它不是在初始化时赋值的,而是在加载视图的时 候。你可以把它设置为普通可选类型,但是如果这个视图加载正确,它是不会为空的。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/993895.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存