前言
Swift的可选类型可以用来表示可能缺失或是计算失败的值。今天和大家分享的笔记是如何有效地利用可选类型,以及它们在函数式编程范式中的使用方法。
为什么使用可选值
Optional是Swift的一个特色,它解决了“有”和“无”这两个困扰了Objective-C许久的哲学概念,同时代码安全性也得到了很大的增加。
Optional的定义
详情参考http://www.jianshu.com/p/a0bf5aa7f21d
Optional的运用
首先先创建一个存储几个欧洲城市的人口数量的字典:
let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]
然后我们来查询某个城市的人口数量:
//let madridPopulation: Int = cities["Madrid"]//无法通过类型检查
let madridPopulation: Int? = cities["Madrid"]
print(madridPopulation)//Optional(3165)
为什么会无法通过类型检查呢?是因为你不能保证每次查询都会有结果,有可能某Madrid键不存在于cities中,所以我们无法保证每次查询都返回的是Int值。所以说madridPopulation的类型为可选类型。
对于一个可选类型的使用,我们可以检查查询是否成功:
if (madridPopulation != nil) {
//强制解包
print("The population of Madrid is \(madridPopulation! * 1000)")
} else {
print("Unknown city: Madrid")
}
如果查询成功,我们设置了一个运算,这时候出现了一个后缀运算符“!”,该运算符是强制将一个可选类型转换为一个不可选类型,是为了获取可选值中实际的Int值。
在开发的时候,有时候会忘记打"!",虽然编译器会告诉你应该添加一个"!",但是还是挺不方便的。未经检验的可选类型强制解包,解开后发现是nil,这时候你的程序将会崩溃,在控制台就会出现该信息:
fatal error: unexpectedly found nil while unwrapping an Optional value
这种强制解包的做法不推荐大家使用,老司机们可能知道Swift有一个特殊的可选绑定(optional binding)机制,它可以显示地处理异常情况,从而避免运行时错误,但是有个缺点就是比较繁琐,判断比较多。
//可选绑定机制
if let madridPopulation = cities["Madrid"] {
print("The population of Madrid is \(madridPopulation * 1000)")
} else {
print("Unknown city: Madrid")
}
Swift还给!运算符提供了一个更安全的替代--??运算符。让我们来看看??运算符的定义吧:
@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T) rethrows -> T
@warn_unused_result
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T?) rethrows -> T?
如果大家看起来有点懵,那就上代码吧:
let num1 = 1 ?? 3//num1 = 1
let num2 = nil ?? 3// num2 = 3
就是操作符??左边不为nil的时候将左边的值赋给num,这个时候对于右边的值,编译器是不管的,不去运算的。为什么对于右边的值可以不用管,这要归功于@autoclosure defaultValue: () throws -> T (Swift标准库中定义通过使用 @autoclosure 类型标签来避开创建显示闭包的需求),再仔细的说就是一言不合上代码:
infix operator ?? { associativity right precedence 110 }
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
if let x = optional {
return x
} else {
return defaultValue()
}
}
总结就是操作符??左边左边不为nil 就将左边的值赋给num,然后就没有然后了。如果左边为nil,这时候就需要去计算右边的值了,再把右边的值赋给num。
可选链
可选链,它是Swift的一个特殊机制。
考虑下面客户订单的模型的代码片段:
struct Order {
let orderNumber: Int
let person: Person?
}
struct Person {
let name: String
let address: Address?
}
struct Address {
let streetName: String
let city: String
let state: String?
}
给定一个Order,如何才能查找到客户的状态呢?
let address = Address(streetName: "软件园", city: "成都", state: nil)
let person = Person(name: "小陈陈", address: address)
let order = Order(orderNumber: 2012090301, person: person)
方法一:使用显示解包运算符
print(order.person!.address!.state!)
这种做法不安全,如果中间任意一个数据缺失,就会导致运行异常,比如说这里的state = nil,执行打印语句就会崩溃。
方法二:可选绑定
if let myPerson = order.person{
if let myAddress = myPerson.address{
if let myState = myAddress.state{
print(myState)
}
}
}
这种做法相当于方法一是安全多了的,但是貌似太繁琐了,要写的判断很多,如果结构再复杂一点,写的判断就更多了。
方法三:可选链
if let myState = order.person?.address?.state{
print("This order will be shipped to \(myState)")
}else {
print("Unknown person, address, or state.")
}
这种做法就是尝试用?运算符去可选类型进行解包,而不是强制将它们解包,当任意一个组成项失败时,整条语句将返回nil。
分支上的可选值
前面我们说的可选绑定机制,有点分支语句的感觉,一步步判断过滤。Swfit还有其他两种分支语句,switch和guard,它们也非常适合与可选值搭配使用。
switch
switch madridPopulation{
case 0?: print("Nobody in Madrid")
case (1..<1000)?: print("Less than a million in Madrid")
case .Some(let x): print("\(x) people in Madrid")
case .None: print("We don't know about Madrid")
}
这里如果有人对.Some()和.None有点懵的小伙伴,请大家再次去看看Optional的定义(http://www.jianshu.com/p/a0bf5aa7f21d)。
如果大家对一个特定的值木有兴趣的话,也可以直接匹配.Some()和.None。
guard
Swift里面的一个神奇的东西,它的设计旨在当一些条件不满足的时候,可以尽早的退出当前作用域。我们重写一下打印给定城市居民数量的代码:
func populationDescriptionForCity(city: String) -> String? {
guard let population = cities[city] else { return nil}
return "The population of Madrid is \(population * 1000)"
}
print(populationDescriptionForCity("Madrid"))//Optional("The population of Madrid is 3165000")
可选映射
从名字我们也可以猜测它是如果可选值是nil,则结果也是nil。不然就执行一些其它运算,类似于:
func incrementOptional(optional: Int?) -> Int? {
guard let x = optional else {return nil}
return x + 1
}
flatMap
flatMap函数检查一个可选值是否为nil,若不是,我们将其传递给参数函数,若是nil。那么结果也是nil,我们用链式调用来举个例子:
func populationOfCapital3(country: String) -> Int? {
return capitals[country].flatMap { capital in
return cities[capital]
}.flatMap { population in
return population * 1000
}
}
总结
类型系统有助于你捕捉难以察觉的细微错误,其中一些错误很容易在开发过程中被发现,但是会一直留存在代码里面,这样就像是埋来一颗炸弹一样,很不安全。坚持使用可选值能够从根本上杜绝这种错误。