Swift的Enum&Optional&运算符重载

532 阅读5分钟

1、Enum

1.1、Enum特性

  • Swift 中的枚举比 OC 中更加灵活,不需给枚举中的每一个成员都提供值;并且枚举成员可以是字符串、字符、任意的整数值,或者是浮点类型。 image.png
    • .rawValue : 取 case的原始值,与枚举值打印出的内容可能相同,但本质不同

    • 在硬编码符号表里获得 image.png

  • 查看SIL
    • 使用了可失败初始化器 image.png
    • alloc_box开辟堆区内存,得到rawValue_allocateUninitializeArray<A>(_:)方法开辟连续内存空间,并将枚举值存进去 image.png
  • 枚举还可以 定义方法(方法需要变异: mutating)、属性Extension遵循协议

1.2、关联值

  • 通过()中写入 参数及其类型,使枚举值携带更多信息,并且不能再赋予初始值 image.png
    • 网络请求就可以多使用这种枚举

1.3、Enum的大小

  • 在 Swift 中进行枚举布局的时候一直是尝试使用最少的空间来存储 enum,也就是说,如果在系统能明确一个 case值 的空间有浪费时,就会尝试用其他case填满这个空间
1.3.1、No-payload enums
  • 没有关联值的,默认用Int8类型存储,每个case占1位,UInt8 能够表示 256 cases,也就意味着如果一个默认枚举类型且没有关联值的 case 少于 256,当前枚举类型的大小都是 1 字节。
1.3.2、Single-payload enums
  • 1个负载时,会使用负载类型中的额外空间来记录没有负载的 case值。Bool类型是 1 字节,也就是 UInt8,1字节有8位,表达Bool值的内容只需要1位低位的 0, 1 这两种情况,剩余7位就可以给其他case用)
    enum LZEnumcase test_one(Boolcase test_two 
        case test_three 
        case test_four 
    }
    
1.3.3、Mutil-payload enums
  • 多负载时,一般来说枚举类型的大小取决于当前最大关联值的大小

    enum LZEnumcase test_one(Bool)
        case test_two(Intcase test_three 
        case test_four 
    }
    当前 LZEnum 的大小就等于 sizeof(Int) + sizeof(rawVlaue) = 9
    
    enum LZEnumcase test_one(Bool)
        case test_two(Int, Int, Intcase test_three 
        case test_four 
    }
    当前 LZEnum 的大小就是 sizeof(Int) * 3 + sizeof(rawVlaue) = 25
    
  • 源码查看 image.png

    • getEnumTagCounts(): image.png

1.4、indirect

  • Enum 属于值类型,在编译时大小就应该确定,但要做到像下边的 二叉树 类型,关联值还是一个枚举时无法在编译时确定大小,所以就需要使用到 indirect 关键字 把这个枚举分配到堆空间上 image.png

  • 开发中也可以使用这种方式来编写 链表 image.png

2、Optional

  • 允许被修饰的数据为空,可以写为: var name: Int? 或者 var name: Optional<Int>

2.1、可选值的作用

  • 源码中查看其本质其实是一个 Enum,我们可以利用当前编译器的类型检查来达到语法书写层面的安全性
    @frozen 
    public enum Optional<Wrapped>: ExpressibleByNilLiteralcase none 
        case some(Wrapped) 
    }
    
    image.png

2.2、if let

  • 如果每一个可选值都用模式匹配的方式来获取值,在代码书写上就比较繁琐,我们还可以使用 if let 的方式来进行 可选值绑定
    if let name = nameTextField.text {
        print("\(name)")
    }
    

2.3、guard let

  • 和 if let 刚好相反,guard let 守护一定有值,如果没有,直接返回
    guard let name = nameTextField.text, name != "" 
        else {
            print("name为空值")
            return
        }
    }
    

2.4、可选链

  • 我们都知道在 OC 中我们给一个 nil 对象发送消息什么也不会发生,Swift 中我们是没有办法向一个 nil 对象直接发送消息,但是借助 可选链 可以达到类似的效果
    //语法调用
    let str: String? = "Lz" 
    let upperStr = str?.uppercased().lowercased()
    
    //函数调用
    var closure: ((Int) -> ())? 
    closure?(1)    // closure 为 nil 不执行
    
    //下标
    let dict = ["one": 1, "two": 2] 
    dict?["one"]   // Optional(1) 
    dict?["three"] // nil
    
    • 如果可选内容为空,则直接不执行后边方法,有内容时才执行,对于下标和函数调用也适用

2.5、?? 运算符 (空合并运算符)

  • ( a ?? b ) 将对 可选类型a 进行空判断,如果a有值就返回a,没值就返回b
    • 表达式 a 必须是 Optional 类型 
    • 默认值 b 的类型必须要和 a 存储值的类型一致

2.6、隐式解析可选类型

  • 隐式解析可选类型是可选类型的一种,使用的过程中和非可选类型无异。它们之间唯一的区别是,隐式解析可选类型会向 Swift编译器 打包票在访问值的时候绝对不会为 nil,如果确实为 nil 了那么就会崩溃
    • 可选类型使用时需要!进行解包
    • 隐式解析可选类型使用时不需解包,像一般参数一样使用
    var age1: Int//可选类型
    var age2: Int//隐式解析可选类型
    
    let x = age1! + 10  //需要解包
    let y = age2 + 10   //无需解包
    

3、运算符重载

3.1、已有运算符重定义

  • 需要注意 prefixinfixpostfix修饰的运算符可能名称一样,但效果不一样,比如减法和负数符号,都是“-”,但意义不同 image.png

3.2、定义新运算符

  1. 可以创建一个优先级组,规定优先级与结合性;也可使用系统自带的优先级组

  2. 定义 运算符名称运算位置 : 前置、后置还是中置运算符,与遵循的 优先级组

    • 优先级组表明一组符号运算时先算哪个,有 加法级组AdditionPrecedence乘法级组MultiplicationPrecedence位运算级组 BitwiseShiftPrecedence 等等,可在 苹果官方Swift文档 中查看
  3. 实现自定义运算符方法 image.png

可能的报错
Adjacent operators are in unordered precedence groups 'MultiplicationPrecedence' and 'LZprecedence'
  • 中间出现过一个错误,原因是定义的 +++ 运算符虽然指定了 LZprecedence 优先级高于加法优先级,但未规定和乘法优先级的优先顺序,就出现了该报错 image.png
自定义依赖符

image.png