11.6 可选类型与可选链

有时候我们在Swift程序表达式中会看到“?”和“!”等符号,它们代表什么含义呢?这些符号都与可选类型相关,这一节我们就来详细介绍一下。

11.6.1 可选类型

有时候我们使用一个变量或常量,它保存的值可能有也可能没有。例如下列代码:

  1. func divide(n1 : Int, n2 : Int) ->Double? {
  2. if n2 == 0 {
  3. return nil
  4. }
  5. return Double(n1)/Double(n2)
  6. }
  7. let result : Double? = divide(100, 200)

上述代码第①行使用了divide函数进行除法运算,在第二个参数n2为零的情况下,函数返回nil,所以第③行代码获得函数返回值要么有值,要么没有值(等于nil的情况)。为了能够接收这种不确定的返回值,我们需要在类型后面加上问号(?),表示该类型是可选类型,在本例中是Double?

  1. 可选绑定

可选类型可以用于判断,如下代码所示:

  1. if let result2 : Double? = divide(100, 0) {
  2. println("Success.")
  3. } else {
  4. println("failure.")
  5. }

我们在第①行调用函数进行计算,然后把结果直接赋值给变量或常量。如果result2不等于nil,则if语句的逻辑表达式为true。以上代码的判断输出结果如下:

  1. failure.

这种可选类型在ifwhile语句中赋值并进行判断的写法,叫做可选绑定

  1. 强制拆封

如果我们能确定可选类型一定有值,那么在读取它的时候,可以在可选类型的后面加一个感叹号(!)来获取该值。这种感叹号的表示方式称为可选值的强制拆封(forced unwrapping)。如下代码所示:

  1. let result1 : Double? = divide(100, 200)
  2. println(result1!)

println(result1!)语句中的result1!就进行了强制拆封。

  1. 隐式拆封

为了能够方便地访问可选类型,我们可以将可选类型后面的问号(?)换成感叹号(!),这种可选类型在拆封时变量或常量后面不加感叹号(!)的表示方式称为隐式拆封。如下代码所示:

  1. let result3 : Double! = divide(100, 200)
  2. println(result3)

在变量或常量声明的时候,数据类型Double 后面跟的是感叹号(!)而不是问号(?),在拆封的时候,变量或常量后面不用加感叹号(!),这就是隐式拆封。隐式拆封的变量或常量使用起来就像普通变量或常量一样,你也可以把它看成是普通的变量或常量。

11.6.2 可选链

在介绍可选链之前,我们先看一个类图(见图11-2)。

{%}

图 11-2 关联关系的类图

这个类图在图11-1类图的基础上增加了公司类(Company),把Department修改为类。它们之间是典型的关联关系类图。这些类一般都是实体类,实体类是系统中的人、事、物。Employee通过dept属性与Department关联,Department通过comp属性与Company关联。

下面看示例代码:

  1. class Employee {
  2. var no : Int = 0
  3. var name : String = "Tony"
  4. var job : String?
  5. var salary : Double = 0
  6. var dept : Department = Department()
  7. }
  8. class Department {
  9. var no : Int = 10
  10. var name : String = "SALES"
  11. var comp : Company = Company()
  12. }
  13. class Company {
  14. var no : Int = 1000
  15. var name : String = "EOrient"
  16. }
  17. var emp = Employee()
  18. println(emp.dept.comp.name)

上述代码第①行定义了Employee类,第③行定义了Department类,第⑤行定义了Company类。第②行代码var dept : Department = Department()关联到Department类。第④行代码var comp : Company = Company()关联到Company类。

给定一个Employee实例(见第⑥行代码),通过第⑦行代码emp.dept.comp.name可以引用到Company实例,形成一个引用的链条,但是这个“链条”任何一个环节“断裂”(为nil)都无法引用到最后的目标(Company实例)。

事实上,第②行代码是使用Department()构造器实例化dept属性的,这说明给定一个Employee实例,一定会有一个Department与其关联。但是现实世界并非如此,一个新入职的员工未必有部门,这种关联关系有可能有值,也有可能没有值,我们需要使用可选类型(Department?)声明dept属性。第④行代码的comp属性也是类似的。

修改代码如下:

  1. class Employee {
  2. var no : Int = 0
  3. var name : String = "Tony"
  4. var job : String?
  5. var salary : Double = 0
  6. var dept : Department?
  7. }
  8. class Department {
  9. var no : Int = 10
  10. var name : String = "SALES"
  11. var comp : Company?
  12. }
  13. class Company {
  14. var no : Int = 1000
  15. var name : String = "EOrient"
  16. }

在第①行代码中声明deptDepartment?可选类型,第②行代码声明compCompany? 可选类型。那么原来的引用方式emp.dept.comp.name已经不能应对可选类型了。我们在前面介绍过可选类型的引用,可以使用感叹号(!)进行强制拆封,代码修改如下:

  1. println(emp.dept!.comp!.name)

但是强制拆封有一个弊端,如果可选链中某个环节为nil,将会导致代码运行时错误。我们可以采用更加“温柔”的引用方式,使用问号(?)来代替原来感叹号(!)的位置,如下所示:

  1. println(emp.dept?.comp?.name)

问号(?)表示引用的时候,如果某个环节为nil,它不会抛出错误,而是会把nil返回给引用者。这种由问号(?)引用可选类型的方式就是可选链

可选链是一种“温柔”的引用方式,它的引用目标不仅仅是属性,还可以是方法、下标和嵌套类型等。

下面我们看一个具有嵌套类型的示例:

  1. class Employee {
  2. var no : Int = 0
  3. var name : String = ""
  4. var job : String = ""
  5. var salary : Double = 0
  6. var dept : Department?
  7. struct Department {
  8. var no : Int = 10
  9. var name : String = "SALES"
  10. }
  11. }
  12. var emp = Employee()
  13. println(emp.dept?.name)

上述代码第①行定义可选类型Department?的属性deptDepartment是嵌套结构体类型。在第③行采用可选链方式引用。

输出结果为nil,这是因为emp.dept环节为nil。如果把第①行代码修改一下:

  1. var dept : Department? = Department()

则输出结果为SALES。这说明可选链可以到达目标name

至于如何为其他类型加可选链,我们会在后面的学习过程中逐个介绍。

本节在介绍可选类型和可选链时,多次使用了问号(?)和感叹号(!),但是它们的含义是不同的,下面我们详细说明一下。

  1. 可选类型中的问号(?

声明这个类型是可选类型,访问这种类型的变量或常量时要使用感叹号(!),下列代码是强制拆封:

  1. let result1 : Double? = divide(100, 200) //声明可选类型
  2. println(result1!) //强制拆封取值
  1. 可选类型中的感叹号(!

声明这个类型也是可选类型,但是访问这种类型的变量或常量时可以不使用感叹号(!),下列代码是隐式拆封:

  1. let result3 : Double? = divide(100, 200) //声明可选类型
  2. println(result3) //隐式拆封取值
  1. 可选链中的感叹号(!

多个对象具有关联关系,当从一个对象引用另外对象的方式、属性和下标等成员时就会形成引用链,由于这个“链条”某些环节可能有值,也可能没有值,因此需要采用如下方式访问:

  1. emp.dept!.comp!.name
  1. 可选链中的问号(?

在可选链中使用感叹号(!)访问时,一旦“链条”某些环节没有值,程序就会发生异常,于是我们把感叹号(!)改为问号(?),代码如下所示:

  1. emp.dept?.comp?.name

这样某些环节没有值的时候返回nil,程序不会发生异常。