前言
Swift的可選類型可以用來表示可能缺失或是計算失敗的值。今天和大家分享的筆記是如何有效地利用可選類型,以及它們在函數(shù)式編程范式中的使用方法。
為什么使用可選值
Optional是Swift的一個特色,它解決了“有”和“無”這兩個困擾了Objective-C許久的哲學(xué)概念,同時代碼安全性也得到了很大的增加。
Optional的定義
詳情參考http://www.lxweimin.com/p/a0bf5aa7f21d
Optional的運用
首先先創(chuàng)建一個存儲幾個歐洲城市的人口數(shù)量的字典:
let cities = ["Paris": 2241, "Madrid": 3165, "Amsterdam": 827, "Berlin": 3562]
然后我們來查詢某個城市的人口數(shù)量:
//let madridPopulation: Int = cities["Madrid"]//無法通過類型檢查
let madridPopulation: Int? = cities["Madrid"]
print(madridPopulation)//Optional(3165)
為什么會無法通過類型檢查呢?是因為你不能保證每次查詢都會有結(jié)果,有可能某Madrid鍵不存在于cities中,所以我們無法保證每次查詢都返回的是Int值。所以說madridPopulation的類型為可選類型。
對于一個可選類型的使用,我們可以檢查查詢是否成功:
if (madridPopulation != nil) {
//強(qiáng)制解包
print("The population of Madrid is \(madridPopulation! * 1000)")
} else {
print("Unknown city: Madrid")
}
如果查詢成功,我們設(shè)置了一個運算,這時候出現(xiàn)了一個后綴運算符“!”,該運算符是強(qiáng)制將一個可選類型轉(zhuǎn)換為一個不可選類型,是為了獲取可選值中實際的Int值。
在開發(fā)的時候,有時候會忘記打"!",雖然編譯器會告訴你應(yīng)該添加一個"!",但是還是挺不方便的。未經(jīng)檢驗的可選類型強(qiáng)制解包,解開后發(fā)現(xiàn)是nil,這時候你的程序?qū)罎ⅲ诳刂婆_就會出現(xiàn)該信息:
fatal error: unexpectedly found nil while unwrapping an Optional value
這種強(qiáng)制解包的做法不推薦大家使用,老司機(jī)們可能知道Swift有一個特殊的可選綁定(optional binding)機(jī)制,它可以顯示地處理異常情況,從而避免運行時錯誤,但是有個缺點就是比較繁瑣,判斷比較多。
//可選綁定機(jī)制
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的時候?qū)⒆筮叺闹蒂x給num,這個時候?qū)τ谟疫叺闹担幾g器是不管的,不去運算的。為什么對于右邊的值可以不用管,這要歸功于@autoclosure defaultValue: () throws -> T (Swift標(biāo)準(zhǔn)庫中定義通過使用 @autoclosure 類型標(biāo)簽來避開創(chuàng)建顯示閉包的需求),再仔細(xì)的說就是一言不合上代碼:
infix operator ?? { associativity right precedence 110 }
func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
if let x = optional {
return x
} else {
return defaultValue()
}
}
總結(jié)就是操作符??左邊左邊不為nil 就將左邊的值賦給num,然后就沒有然后了。如果左邊為nil,這時候就需要去計算右邊的值了,再把右邊的值賦給num。
可選鏈
可選鏈,它是Swift的一個特殊機(jī)制。
考慮下面客戶訂單的模型的代碼片段:
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,如何才能查找到客戶的狀態(tài)呢?
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!)
這種做法不安全,如果中間任意一個數(shù)據(jù)缺失,就會導(dǎo)致運行異常,比如說這里的state = nil,執(zhí)行打印語句就會崩潰。
方法二:可選綁定
if let myPerson = order.person{
if let myAddress = myPerson.address{
if let myState = myAddress.state{
print(myState)
}
}
}
這種做法相當(dāng)于方法一是安全多了的,但是貌似太繁瑣了,要寫的判斷很多,如果結(jié)構(gòu)再復(fù)雜一點,寫的判斷就更多了。
方法三:可選鏈
if let myState = order.person?.address?.state{
print("This order will be shipped to \(myState)")
}else {
print("Unknown person, address, or state.")
}
這種做法就是嘗試用?運算符去可選類型進(jìn)行解包,而不是強(qiáng)制將它們解包,當(dāng)任意一個組成項失敗時,整條語句將返回nil。
分支上的可選值
前面我們說的可選綁定機(jī)制,有點分支語句的感覺,一步步判斷過濾。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.lxweimin.com/p/a0bf5aa7f21d)。
如果大家對一個特定的值木有興趣的話,也可以直接匹配.Some()和.None。
guard
Swift里面的一個神奇的東西,它的設(shè)計旨在當(dāng)一些條件不滿足的時候,可以盡早的退出當(dāng)前作用域。我們重寫一下打印給定城市居民數(shù)量的代碼:
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,則結(jié)果也是nil。不然就執(zhí)行一些其它運算,類似于:
func incrementOptional(optional: Int?) -> Int? {
guard let x = optional else {return nil}
return x + 1
}
flatMap
flatMap函數(shù)檢查一個可選值是否為nil,若不是,我們將其傳遞給參數(shù)函數(shù),若是nil。那么結(jié)果也是nil,我們用鏈?zhǔn)秸{(diào)用來舉個例子:
func populationOfCapital3(country: String) -> Int? {
return capitals[country].flatMap { capital in
return cities[capital]
}.flatMap { population in
return population * 1000
}
}
總結(jié)
類型系統(tǒng)有助于你捕捉難以察覺的細(xì)微錯誤,其中一些錯誤很容易在開發(fā)過程中被發(fā)現(xiàn),但是會一直留存在代碼里面,這樣就像是埋來一顆炸彈一樣,很不安全。堅持使用可選值能夠從根本上杜絕這種錯誤。