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
可选绑定,switch
和guard
也非常适合与可选值搭配使用。
在switch
语句中匹配可选值,可以简单地为case
分支的每个模式添加一个?
后缀,如果对特定值没有兴趣,也可以匹配Optional
的Some
值和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 let
和guard 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。
坚持使用可选值能够从根本上杜绝这类错误。