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。
堅持使用可選值能夠從根本上杜絕這類錯誤。