Swift 可选型

1、可选型

  • Swift 语言为我们提供了一种全新的、更加安全的类型 “可选型”。可选型是使用范型枚举的形式来组织的,也就是说此特性可以运用于所有的类型、结构体、类或者其他复杂数据类型。

    • Swift 中的可选型会在编译时就去检查某些可能发生在运行时的错误,可以提高代码的质量。
    • 可选型用于某些不确定是否有值的情况,其有两个值,即 “具体的值” 和 nil
    • 当一个变量、常量或者其他类中存储有值的时候返回里面存储的值,没有值的时候返回 nil(表示空值)。
    • Swift 中把一个变量定义为可选型,在没有赋值的情况下它会被默认赋值为 nil
    • 在 OC 中 nil 表示的是一个指向不存在对象的指针,而 Swift 中表示空的关键字为 nil,没有其他含义。
    • 在 Swift 中不能给一个可选型之外的类型赋值为 nil
  • 可选的声明

    • 可选的标准声明形式,直接在程序中使用变量类型紧跟 ?

      1
      var age: Int?
    • 可选的显式声明形式

      1
      var age: Optional<Int>
  • 相比单纯的 nil,使用可选型封装的 nil 给了开发者更合理的机会去处理空值:选择忽略空值,或者为可能出现空值的对象赋上默认值(使用 ?? 操作符)。

  • 在条件足够完备的时候,尽量不要定义一个可选型,比如网络通信是一个复杂的环境,很多因素是不可控的,所以如果程序中需要通过网络采集数据,那么数据的借口可能是可选型的,最好不要把你的数据模型也声明成可选型,虽然这样可以减少网络数据到本地数据映射时的代码量。更好的方法是对可选型使用 ?? 赋值,这样在网络数据返回空值的时候,你的数据模型对应的属性就是初始化时设置的默认值。

2、强制解包可选

  • Swift 中把一个变量定义为可选型,在没有赋值的情况下它会被默认赋值为 nil,所以即便定义了 age 的时候不赋值,它也已经被初始化了,它的值是 nil,直到你通过赋值更改它的值。

    1
    2
    3
    var age: Int?
    age = Int("12")
    print("age is \(age)")
    • 在输出语句中我们得到 age 的值显示为 Optional(12)Optional 代表 “可选”,age 的当前值为一个整数类型的可选型。
    • 在实际开发中我们真正需要的是括号中的 12,要想获得这个 12,就需要使用 “解包” 操作。
  • 解包是针对于可选类型的变量操作,当我们确定一个可选类型的值不为 nil 的时候,可以使用变量名紧跟 ! 的方式强制获取类型可选中的值,从而省略判断步骤,这种机制叫做 “强制解包可选”。

    1
    2
    3
    var age: Int?
    age = Int("12")
    print("age is \(age!)")
    • 此时在输出语句中我们得到 age 的值显示为 12。
  • 但是如果这个变量中没有值,那么在解包的时候程序就会崩溃。

3、可选绑定

  • 想比于强制解包,Swift 提供了一种更安全的解包方式 “可选绑定”。可选绑定有两种格式 if-letguard-let-else

  • 可选绑定本身代表了两个步骤:解包和拷贝,如果你需要一个解包后的变量拷贝的话,可以使用 if-varguard-var)的语法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let firstStr: String? = "hello"
    let secondStr: String? = "world"

    if var fs = firstStr, let ss = secondStr {

    print(fs + " " + ss) // hello world

    fs = "hi" // 修改

    print(fs + " " + ss) // hi world
    }

3.1 if-let 格式

  • if-let 格式

    1
    2
    3
    4
    5
    6
    7
    var age: Int?

    if let value = age {

    // 在内部使用解包后的值
    print("\(value)")
    }
    • 如果 age 有值,对 age 解包并给其一个 “别名” value,在 if 后的大括号对中 vaule 才有效果。
    • 如果 age 无值,即为 nil 时,那么大括号对中的代码不会执行,使可选解包程序不会崩溃。

3.2 guard-let-else 格式

  • guard-let-else 格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var age: Int?

    guard let value = age else {

    // 终止方法
    return
    }

    // 在外部使用解包后的值
    print("\(value)")
    • guard-let-else 首先处理 age 为 nil 时的情况,在此种情况下必须在大括号中使用 return 或者 break 提前终止代码。
    • 如果 age 有值,那么 age 的值也会被保存在 “别名” value 中,不管有多少个 guard-let-else,“别名” value 的作用域都在最外层。

3.3 可选绑定嵌套

  • 在 Swift 1.2 之前

    1
    2
    3
    4
    5
    6
    7
    if let a = b {

    if let c = d {

    ...
    }
    }
  • 在 Swift 1.2 之后,可以把所有的可选绑定放到一个 if-let 中,使用逗号隔开。

    1
    2
    3
    if let a = b, c = d, ... {

    }
  • 在 Swift 3.0 之后,可选绑定和普通的判断语句可以混用到一个 if 语句中,所以每一次可选绑定都需要用 let 关键字来声明。

    1
    2
    3
    if let a = b, let c = d, ... {

    }

4、隐式解包

  • 隐式解包是一种特殊的可选型,在系统中由于一些历史原因出现这种类型,但是我们自己最好不要定义这样的类型。

    • 将可选变量声明中的 ? 改为 ! 来标注一个隐式解包可选。
    • 隐式解包可选用于一个确定会有值的可选类型实例声明。

      1
      var age: Int! = 18
  • 隐式解包的类型可以和普通的类型进行运算,此时它会被解包,结果返回的也是非可选型的类型。

    1
    2
    3
    let a: Int! = 3

    let b = a + 1 // b 的类型是 Int,值为 4
  • 在 Swift 2.2 中,隐式解包的实例传递的对象的类型依旧是隐式解包类型的,并且和普通类型所组成的数组也会被推断为隐式解包的数组类型。

    1
    2
    3
    4
    let a: Int! = 3

    let c = a // c 的类型依旧为 Int!
    let arr = [1, a] // 数组 arr 的类型是 [Int!] 类型的
  • 在 Swift 3.0 及以后,隐式解包的类型在传递时都会被当作普通的可选型,只有在运算这种需要解包的时候才会被解包。

    1
    2
    3
    4
    let a: Int! = 3

    let c = a // c 的类型依旧为 Int?
    let arr = [1, a] // 数组 arr 的类型是 [Int?] 类型的
  • 在 Swift 工程中,最常见的隐式解包就是 IBOutlet

    1
    IBOutlet weak var dateLabel: UILabel!
    • IBActionIBOutlet 允许你的代码和 Interface Builder 中的元素进行交互,通过隐式解包的声明方式。
    • 当 Swift 代码对象初始化之后,storyboard 中的元素会在运行时与 IBOutlet 对象对接,这也是不能在 prepareForSegue 方法中访问目标控制器中的 IBOutlet 元素的原因,因为在初始化时会造成对 nil 的解包导致程序崩溃。

5、可选运算符

  • 可选运算符 ?? 之前是一个可选型,?? 之后是一个非可选型的值。?? 操作符自带解包功能,在赋值时 ?? 会检查其之前的可选型。

    1
    2
    var age: Int?
    var value = age ?? -1
    • 如果可选型不为 nil 则将其解包并返回。
    • 如果为 nil,则不会返回 nil,此时返回 ?? 之后的非可选型的值。

6、可选链

  • 针对可选型结果中仍有可选型的情况,可以使用可选链操作,将可选的调用使用 ? 连接在一起形成一个链,如果可选链中任何一个部位的可选型是空值,则可选链会中断,而不会引发强制解包可选时发生的错误。

    • 除了可以对可选型中的属性进行判空外,可选链更适用于可选型中的方法调用。
    • 如果可选链末尾是一个方法,那么当链条提前中断的时候,该方法不会执行。
    • 可选链可以多层可选(嵌套)。
  • 有一个类 Student,里面有两个属性 nameage,它们的类型是可选型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Student {
    var name: String?
    var age: Int?
    }

    func example(code: String) -> Student? {

    if code == "xm" {

    let xiaoming: Student = Student()
    xiaoming.name = "XiaoMing"
    xiaoming.age = 12
    return xiaoming

    } else if code == "xg" {

    let xiaogang: Student = Student()
    xiaogang.name = "XiaoGang"
    xiaogang.age = 13
    return xiaogang
    }

    return nil
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用可选绑定来判断
    if let student = example(code: "xm") {

    // 使用可选绑定来判断
    if let age = student.age {

    let year = 18 - age
    print(year)
    }
    }
  • 上面的代码运行没有问题,但是使用了两个可选绑定,因此可以使用一个 if let 即使用可选链来化简。

    1
    2
    3
    4
    5
    6
    // 使用可选链来判断
    if let age = example(code: "xm")?.age {

    let year = 18 - age
    print(year)
    }
  • 可选绑定与可选链的使用

    • 如果需要频繁的使用一个可选型的值,那么可选绑定的代价是和算的。
    • 如果需要在某个可选型有值的时候调用它内部的方法,如果是空值则不调用,那么使用可选链的方式会更加简单。
  • 可选绑定的理念是忽略空值,而可选链中可以使用空值,比如当空值出现时使用 ?? 把空值转换成常量。

    1
    let age = example(code: "lw")?.age ?? 0

7、Map 和 FlatMap

  • 可选型中有两个非常实用的方法 mapflatMap,当你确定需要返回一个可选型,并且希望当可选型不为空时,对可选型的值做某些处理后再返回,可以选用 map

  • 可选型的 mapflatMap 的定义如下

    1
    2
    3
    public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

    public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
  • 在不确定某个学生的年龄是否有值的情况下,我们定义一个 isAdult 方法,它的作用是判断那些有年龄信息的学生的年龄是否成人,对于年龄信息未登记的学生,则返回 nil

    1
    2
    3
    4
    5
    6
    7
    8
    func isAdult(stu: Student) -> Bool? {

    let result = stu.age.map {
    $0 >= 18 ? true : false
    }

    return result
    }
    1
    2
    3
    4
    5
    let xm = Student()
    xm.age = 3
    xm.name = "XiaoMing"

    print(isAdult(stu: xm) ?? -1) // false
    • 当某个学生的年龄存在时,参数的 map 方法才会执行。
    • 和可选绑定一样,map 方法体中的参数已经默认解包了,所以这里的 $0 即指 stu.age 解包后的值,是 Int 类型的。
  • 如果使用 map 时,map 方法体中的返回值本身也是一个可选型,则返回结果会出现多重可选型。

    1
    2
    3
    4
    let result = example(code: "xm").map {

    isAdult(stu: $0)
    }
    • 由于在 map 方法体中的方法 isAdult 返回值也是可选型的,所以 result 的类型是 Bool??,在使用时我们被迫需要做两次空值判断,而这些操作并没有意义,我们关心的只是结果。
  • flatMap 会把多重可选型化简成一个可选型。

    1
    2
    3
    4
    let result = example(code: "xm").flatMap {

    isAdult(stu: $0)
    }
文章目录
  1. 1. 1、可选型
  2. 2. 2、强制解包可选
  3. 3. 3、可选绑定
    1. 3.1. 3.1 if-let 格式
    2. 3.2. 3.2 guard-let-else 格式
    3. 3.3. 3.3 可选绑定嵌套
  4. 4. 4、隐式解包
  5. 5. 5、可选运算符
  6. 6. 6、可选链
  7. 7. 7、Map 和 FlatMap
隐藏目录