Swift的可选类型可以用来表示可能缺失或是计算失败的值。

案例:字典

无法保证字典查询操作总是返回一个值,Swift可选类型可以表示这种失败的可能性:

let citys = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]
let madridPopulation: Int? = citys["Madrid"]

Swift可选类型解包方式:

  • 可选绑定
  • 使用!强制解包
  • 隐式解包
  • 使用??提供一个默认值
// 强制解包
print(madridPopulation!)

// 隐式解包
let v: Int! = madridPopulation
print(v*10)

// ?? 运算符提供默认值
let v1 = madridPopulation ?? 100
print(v1)

没有检验的强制解包和隐式解包都是糟糕的代码意味,预示着可能发生运行时错误。所以建议使用可选绑定和??运算符。

使用??运算符需要提供一个默认值,当结果为nil时,这个默认值将作为返回值。简单说,它可以如下定义:

// 定义运算符
infix operator ???
func ???<T>(optional: T?, defaultValue: T) -> T {
    if let result = optional {
        return result
    } else {
        return defaultValue
    }
}

上面的定义有一个问题,如果默认值是通过某个函数或表达式得到的,那么无论可选值是否为nil,defaultValue都会求值。所以应该只在可选参数为nil时才对defaultValue参数求值,可以按如下方式解决这个问题:

infix operator ????
func ????<T>(optional: T?, defaultValue: () -> T) -> T {
    if let result = optional {
        return result
    } else {
        return defaultValue()
    }
}

let v3 = madridPopulation ???? { 100 }
print(v3)

美中不足的是现在的defaultValue参数是一个显式闭包,使用Swift的autoclosure类型标签可以避开创建显式闭包:

infix operator ?????: AdditionPrecedence
func ?????<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
    if let result = optional {
        return result
    } else {
        return defaultValue()
    }
}

let v4 = madridPopulation ????? 100
print(v4)

可选值链

Swift有一个特殊的机制,可选值链。用于被嵌套的类、结构体中对方法和函数进行选择。考虑如下客户订单模型代码片段:

struct Address {
    let city: String
    let state: String?
}
struct Person {
    let name: String
    let address: Address?
}
struct Order {
    let orderNumber: String
    let person: Person?
}
let order = Order(orderNumber: "123456789", person: Person(name: "张三", address: Address(city: "北京", state: "长安街")))

给定一个order,如何获取订单的用户所在的街道信息?

  • 使用强制解包非常不安全(极易引起运行时异常)
  • 使用可选绑定相对更安全但是显得麻烦
  • 使用可选链(一个组成项为nil时,整个语句链返回nil)就更加安全简洁
// 强制解包
print(order.person!.address!.state!)

// 可选绑定
if let person = order.person {
    if let address = person.address {
        if let state = address.state {
            print(state)
        }
    }
}

// 可选连解包
if let state = order.person?.address?.state {
    print(state)
}

分支上的可选值

除了if let可选绑定,switchguard也非常适合与可选值搭配使用。

switch语句中匹配可选值,可以简单地为case分支的每个模式添加一个?后缀,如果对特定值没有兴趣,也可以匹配OptionalSome值和None值:

// switch语句匹配可选值
let options = [0, 1, 10, 100, nil]
for i in options {
    switch i {
    case 0?:
        print("我是零")
    case (1..<100)?:
        print("一百以内的数")
    case .some(let x):
        print(x)
    case .none:
        print("没有值呀")
    }
}

guard语句的设计旨在当条件不满足时,可以尽早退出当前作用域,语句后面的代码需要值存在才能执行,这是一个很常用的情境,让控制流比使用if let语句更简单:

// guard 语句
func populationDescriptionForCity(city: String) -> String? {
    guard let population = citys[city] else {
        return nil
    }
    
    return "\(city)的人口是\(population)万"
}
print(populationDescriptionForCity(city: "Paris") ?? "")

可选映射

?运算符允许我们选择性的访问可选值的方法或字段,在实际应用中往往都是如果有值就操作它否则返回nil,如下:

func incrementOptional(optional: Int?) -> Int? {
    guard let result = optional else {
        return nil
    }
    
    return result + 1
}

还可以将对可选值的任何运算作为参数传递给map函数,这个函数是Swift标准库的一部分:

extension Optional {
    func map<U>(transform: (Wrapped) -> U) -> U? {
        guard let result = self else {
            return nil
        }
        return transform(result)
    }
}

可选绑定

map函数展示了一种操作可选值的方法,但还有很多其它方法。

如何计算如下两个可选值的和:

let x: Int? = 3
let y: Int? = nil

由于Int?不支持+运算符,可以使用if letguard let

func addOptionals(optionalX: Int?, optionalY: Int?) -> Int? {
    if let xV = optionalX {
        if let yV = optionalY {
            return xV + yV
        }
    }
    
    return nil
}

func addOptionals1(optionalX: Int?, optionalY: Int?) -> Int? {
    guard let xV = optionalX, let yV = optionalY else {
        return nil
    }
    
    return xV + yV
}

还有一种途径能解决上述问题,那就是借助Swift标准库中的flatMap函数。很多类型中都定义了flatMap函数,可选类型的flatMap函数定义如下:

extension Optional {
    func flatMap<U>(transform: (Wrapped) -> U?) -> U? {
        guard let v = self else {
            return nil
        }
        
        return transform(v)
    }
}

使用flatMap函数来重写两个可选值相加:

func addOptionals2(optionalX: Int?, optionalY: Int?) -> Int? {
    optionalX.flatMap { (xV) -> Int? in
        optionalY.flatMap { (yV) -> Int? in
            xV + yV
        }
    }
}

为何使用可选值

Swift使用可选类型增强静态安全,能在代码执行前捕获到错误,有助于避免缺失值导致意外奔溃。

Objective-C中不能区分字典中key存在value不存在和key不存在的情况,除非使用NSNull。

虽然在Objective-C中对nil发送消息是安全的,但是使用nil往往不安全。例如使用nil初始化NSAttributedString会奔溃。

可选类型有助于捕捉一些难以察觉的细微错误,例如Objective-C中对nil调用rangeOfString方法会返回一个属性全为0的结构体,然而NSNotFound被定义为NSIntegerMax。

坚持使用可选值能够从根本上杜绝这类错误。