函數(shù)式Swift之Optional

前言

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),但是會一直留存在代碼里面,這樣就像是埋來一顆炸彈一樣,很不安全。堅持使用可選值能夠從根本上杜絕這種錯誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于 Swift 重要這個文檔所包含的準(zhǔn)備信息, 是關(guān)于開發(fā)的 API 和技術(shù)的。這個信息可能會改變, 根據(jù)這個文...
    無灃閱讀 4,360評論 1 27
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,861評論 1 10
  • Hello Word 在屏幕上打印“Hello, world”,可以用一行代碼實現(xiàn): 你不需要為了輸入輸出或者字符...
    restkuan閱讀 3,210評論 0 6
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出...
    無灃閱讀 1,498評論 0 4
  • 姓名:張漢超 公司:東莞耀升機(jī)電有限公司 組別:4月25-27日六項精進(jìn)245期學(xué)員 【日精進(jìn)打卡第221天】 【...
    張漢超閱讀 140評論 0 0