Swift函數式編程四(可選值)

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可選綁定,switchguard也非常適合與可選值搭配使用。

switch語句中匹配可選值,可以簡單地爲case分支的每個模式添加一個?後綴,如果對特定值沒有興趣,也可以匹配OptionalSome值和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 letguard 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。

堅持使用可選值能夠從根本上杜絕這類錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章