Swift Optional 的使用

Swift Optional

當宣告一個變數,這個變數預設就是 non-optional (非選擇性)的,換句話說就是指派了一個 non-nil (不可為 nil)的值,也就是這個變數必定有值。假如你指派 nil 值給 non-optional 變數,compiler 就會告訴你不能這麼做。
在沒有宣告為 Optional 的情況下:
var message: String = "Hello Swift~" // 沒問題
message = nil // 編譯時期錯誤
假如在類別中宣告了屬性(變數),但是沒有給初始值,會出現沒有指定初始值的錯誤:
class Messenger {
    var m1:String = "Hello Swift"
    var m2:String

    init(){
        m2 = ""
    }
}
m1 因為在宣告的同時也指定初始值,所以編譯上沒問題;而 m2 因為只有宣告而沒給值,就會出現編譯時期的錯誤,你可以像 m1 一樣直接給值,或是在建構式(init)中指定它的初始值。

在 Objective-C 中,以上這兩種範例都不會發生編譯時期錯誤,相對來說,就可能會發生執行時期因為存取 nil 而造成程式崩潰。

但是這不是說在 Swift 語言中就不能指派一個未初始化的屬性,它使用 Optional 來明確的指示某一變數的值有可能是 nil 的情況。要使用 Optional 只要在資料型態後面附加問號 ? 即可。
class Messenger {
    var m1:String = "Hello Swift"
    var m2:String?
}
加上問號後,就可以不指定初始值。這個 m2 在未被指派任何值之前,它的值預設就是 nil。

為什麼要用 Optional

Swift 被設計成型別安全的程式語言,就像前面的範例,Optional 可以達到編譯時期的檢查,可以避免在執行時期才發生的一些常見的錯誤。

讓我們來看看 nil 怎麼讓程式崩潰,以下是一段 Objective-C 程式碼:
- (NSString *)getFruitColor:(NSString *)fruit {
    if ([fruit isEqualToString:@"Apple"]) {
        return @"red";
    } else if ([fruit isEqualToString:@"Banana"]) {
        return @"yellow";
    }

    return nil; // 有回傳 nil 的可能
}

NSString *fruitColor = [self getFruitColor:@"Cherry"]; // 回傳 nil
NSString *text = @"Fruit color is ";
NSString *message = [text stringByAppendingString:fruitColor]; // 要到執行時期才會發生錯誤
NSLog(@"%@", message);
一般來說,只要有回傳 nil 的可能,通常都會先檢查該變數(fruitColor)是否為 nil,不過有時候就是會疏忽。以上範例在執行到 fruitColor 為 nil 的情形時,就會崩潰。

同樣的程式碼,改用 Swift 重寫如下:
func getFruitColor(fruit: String) -> String? {
    if (fruit == "Apple") {
        return "red"
    } else if (fruit == "Banana") {
        return "yellow"
    }

    return nil
}

var fruitColor:String? = getFruitColor("Cherry")
let text = "Fruit color is "
let message = text + fruitColor  // 編譯時期就會出錯
println(message)
Swift 已經聰明的假設最壞的情況下,fruitColor 會是 nil,所以在編譯時期就可以預先判斷這個地方有機會出現錯誤,不修正的話就無法通過編譯。

解開 Optional 的包裝(Unwrapping Optionals)

以 Optional 包裝的變數,可以用驚嘆號 ! 來強制解開包裝,以便取得它的值。

以前面的範例來說,因為 fruitColor 有可能為 nil,所以在編譯時期就會發生錯誤。我們可以加入 != nil 的判斷條件,確定 fruitColor 不是 nil 後直接使用它,但因為它是 Optional 的型態,所以必須多一道解開包裝的手續才能拿到真正的值,程式碼可以改寫如下:
if (fruitColor != nil) {
    let message = text + fruitColor!  // 沒問題了
    println(message)
}
因為有先用 != nil 做判斷,所以可以很大膽的使用 ! 來取值。但是,如果我們少了 != nil 的 if 判斷會怎樣,因為使用 ! 的關係,編譯器假設你自己確定 fruitColor! 是有值的,因此編譯時期並不會發生錯誤,而是在執行時期才會發生(就像前面那個 Objective-C 範例一樣),錯誤訊息會是
fatal error: Can’t unwrap Optional.None
註:在 Swift 中,if 判斷式必須是明確的 Boolean 表達式,所以你不能寫成這樣
if fruitColor {
    //略...
}

Optional 綁定 (Optional Binding)

要取得 Optional 型態的值,除了強制解開包裝這個方法,還有另一個使用綁定的方法,這也是比較簡單且推薦的方法。Optional 綁定可以檢查 Optional 型態的內容是否有值,假如有值,就解開包裝取出該值並儲存到暫時的變數或常數中。

以前面的範例,我們可以改為
if let tmpFruitColor = fruitColor {
    let message = text + tmpFruitColor
    println(message)
}
要使用 Optional Binding,必須同時使用 if let 或 if var,假如 fruitColor 不包含值(即nil),就會略過整個判斷式。因為 Optional Binding 同時會做解開包裝的動作,所以 if letif var 所指定的暫存變數或常數在使用時,並不需要再使用驚嘆號 ! 來強制取值。

因為 Optional Binding 的機制很方便,所以程式碼可以改得更為簡潔,如下:
let text = "Fruit color is "
if let fruitColor = getFruitColor("Cherry") {
    let message = text + fruitColor
    println(message)
}

Optional 鏈 (Optional Chaining)

Optional 鏈提供一個存取值的簡便方法,可以讓程式碼更簡潔,舉例如下:
class Book {
    var title:String?
    var price:Double?
}

func findBook(isbn:Int) -> Book? {
    if (isbn == 111) {
        let book = Book()
        book.title = "AAA"
        book.price = 120

        return book
    } else if(isbn == 222) {
        let book = Book()
        book.title = "BBB"
        book.price = 280

        return book
    }

    return nil
}

//Optional Binding
if let book = findBook(111) {
    if let price = book.price {
        let cost = price * 0.8; // 打8折
        println(cost)
    }
}

//Option Chaining
if let price = findBook(222)?.price {
    let cost = price * 0.8; // 打8折
    println(cost)
}
如果單單使用 Optional Binding 的方式,當你要存取類別中的屬性值時,可能要不斷的使用 if let 做巢狀判斷,寫起來複雜,看起來也不舒服。改為使用 Optional Chaining 可以讓程式碼變得更簡潔。

總結

  • Swift 語言中,變數/常數預設為 non-optional,表示必定有值。
  • optional 可以讓可能發生的執行時期錯誤,提前在編譯時期就發生,讓你儘早改善。
  • 使用 optional 可以讓變數/常數存在兩種情況:有值或 nil。
  • 使用問號(?)可以包裝變數/常數,使用驚嘆號(!)可以強制解開包裝。
  • 使用 if let 或 if var 做 optional binding。

參考資料:

本文網址:http://blog.tonycube.com/2015/09/swift-optional.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

我要留言

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。