[iOS] Swift 初學手冊:可選類型 (Optionals) – iPhone手機開發技術文章 iPhone軟體開發教學課程

 

幾周前 (譯者註:原文發表於6月24日),蘋果發佈瞭一個全新的編程語言: Swift 。從那時起,我一直在閱讀 Swift 官方手冊,並且在 Xcode6 beta 上把玩學習。我開始喜歡上瞭 Swift 的簡潔和語法。我和我的團隊一起學習這門全新的語言,並且將它和 Objective-C 這個有著30年歷史的老夥計進行對比。同時,我們也在努力探索如何能讓初學者們輕松的掌握 Swift 。

兩周前,我們發佈瞭 Swift 基礎教程,在接下來的幾周裡,我們將會寫一系列的教程來介紹 Swift 的新特性。這一周,讓我們來看看可選類型 (Optionals)。

/

概述

在前面的教程裡我有提及可選類型的概念,但是沒有深入講解。

那麼,什麼是可選類型?

在 Swift 中,當我們聲明一個變量的時候,默認情況下是 非可選類型 (non-optional) ,也就是說,你必須指定一個不為 nil 的值。如果你硬是要把一個非可選類型的變量設為 nil ,那麼編譯器就會告訴你:“嘿你不能把它設置成 nil 好嘛”。沒錯就是這樣:

var message: String = Swift is awesome! // OK
message = nil // compile-time error

當然編譯器給出的錯誤消息可就沒這麼友善瞭,一般會顯示Could not find an overload for ‘__conversion’ that accepts the supplied arguments 這種錯誤。同樣,在類中聲明的變量也是這樣,默認情況下是費可選類型的:

class Messenger {
    var message1: String = Swift is awesome! // OK
    var message2: String // compile-time error
}

message2 處你會得到一個編譯錯誤,因為它沒有初始值。對於那些從 Objective-C 一路走來的小夥伴們可能會感覺很意外,在 Objective-C 裡這種情況根本就不會有問題好嘛:

NSString *message = @Objective-C will never die!;
message = nil;

class Messenger {
    NSString *message1 = @Objective will never die!;
    NSString *message2;
}

不過,你也可以在 Swift 中聲明一個沒有初始化的變量, Swift 提供瞭可選類型來表示沒有值的情況。隻需要在聲明的類型後面加上問號 ? 即可:

class Messenger {
    var message1: String = Swift is awesome! // OK
    var message2: String? // OK
}

你也可以給可選類型的變量們賦值,如果不賦值那麼它的值自動就設為 nil 。

緣由

為什麼要這麼設計呢?蘋果官方給出的解釋是,因為 Swift 是一門類型安全的語言。從前面的例子中可以看出, Swift的可選類型會進行編譯檢查,防止一些常見的運行時錯誤。讓我們看一看下面的例子,這樣可以更好地理解。

比如說,在 Objective-C 中有如下代碼:

- (NSString *)findStockCode:(NSString *)company {
    if ([company isEqualToString:@Apple]) {
        return @AAPL;
    } else if ([company isEqualToString:@Google]) {
        return @GOOG;
    }

    return nil;
}

在上面的方法裡,你可以用 findStockCode 方法來獲取到股票的代碼,顯然隻有 Apple 和 Google 的查詢會返回值,其他情況都會返回 nil 。

假設我們在下面的代碼中會調用這個方法:

NSString *stockCode = [self findStockCode:@Facebook]; // nil is returned
NSString *text = @Stock Code - ;
NSString *message = [text stringByAppendingString:stockCode]; // runtime error
NSLog(@%@, message);

這段代碼在編譯時不會有任何問題,但是如果輸入的是 Facbook 則會返回 nil ,導致運行時錯誤。

而在 Swift 裡,和運行時錯誤不用, Swift 會在編譯時就提示錯誤信息,我們可以把上面的代碼在 Swift 中重寫:

func findStockCode(company: String) -> String? {
   if (company == Apple) {
      return AAPL
   } else if (company == Google) {
      return GOOG
   }

   return nil
}

var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
let message = text + stockCode  // compile-time error
println(message)

在上面的代碼裡, stockCode 被定義成瞭可選類型,這意味著它可以有一個 string 的值,也可以為 nil 。代碼無法通過編譯,會提示一個錯誤:value of optional type String? is not unwrapped

正如你在例子中看到的,Swift 的可選類型加強瞭 nil 檢測,為開發者提供瞭編譯時的檢查,合理的使用可選類型可以有效地提高代碼質量。

強制解析

慢著慢著,前面說瞭那麼多好處,但是代碼還是沒通過編譯啊!別急,我們需要檢測一下 stockCode 是否為 nil ,把代碼做如下修改:

var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
if stockCode {
    let message = text + stockCode!
    println(message)
}

和 Objective-C 中類似,我們先對它進行檢測,看看它是不是有值。如果不為 nil ,我們可以在後面加上一個感嘆號 ! 進行解析,這樣 Xcode 就知道:“嗯我可以使用這個值瞭”。在 Swift 裡我們稱之為 強制解析 (forced unwrapping) ,通過感嘆號強制獲取可選類型的真實值。

再回到上面的代碼中。我們隻是在強制解析之前,檢測瞭一下看看變量是否為 nil 而已。這和 Objective-C 中常見的 nil 檢測也沒啥區別啊。如果我忘瞭檢測呢?看下下面的代碼:

var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
let message = text + stockCode!  // runtime error

這樣我們不會得到編譯錯誤,因為我們用瞭強制解析,編譯器已經假定這個可選類型肯定有值。顯然這樣是錯誤的,運行的時候會得到如下錯誤:

fatal error: Can’t unwrap Optional.None

可選綁定

除瞭強制解析,可選綁定 (optional binding) 是一個更值得推薦的解析方案。 你可以用可選綁定來檢測一個可選類型的值有沒有值,如果有值則解析出來並存儲到一個臨時的變量裡。

廢話少說,放碼過來!讓我們來看看下面這個使用瞭可選綁定的示例代碼:

var stockCode:String? = findStockCode(Facebook)
let text = Stock Code - 
if let tempStockCode = stockCode {
    let message = text + tempStockCode
    println(message)
}

代碼中的 if let (或者 if var ) 是可選綁定的兩個關鍵詞。翻譯成人類語言,大概是這個樣子:“如果 stackCode 它有值,把它的值存到 tempStackCode 裡,然後繼續執行接下來的代碼塊。如果它沒值,跳過後面的代碼塊。” 因為tempStockCode 是一個新的常量,所以你不再需要添加 ! 後綴。

你可以把方法調用放在 if 裡,這樣代碼看起來更簡潔:

let text = Stock Code - 
if var stockCode = findStockCode(Apple) {
    let message = text + stockCode
    println(message)
}

這裡, stockCode 不再是可選類型,我們可以直接使用。如果 findStockCode 方法返回瞭 nil 則會跳過後面的代碼塊。

可選鏈

在解釋可選鏈之前,我們先對原始代碼做一些小小的修改。我們創建一個新的類叫做 Stock ,它有 codeprice 兩個可選類型的屬性。findStockCode 函數用來返回一個 Stock 對象,而不是一個 String 對象:

class Stock {
    var code: String? 
    var price: Double? 
}

func findStockCode(company: String) -> Stock? {
    if (company == Apple) {
        let aapl: Stock = Stock()
        aapl.code = AAPL
        aapl.price = 90.32

        return aapl

    } else if (company == Google) {
        let goog: Stock = Stock()
        goog.code = GOOG
        goog.price = 556.36

        return goog
    }

    return nil
}

接下來,我們先用 findStockCode 函數查找股票的代碼,然後計算購買100股所需要的總價:

if let stock = findStockCode(Apple) {
    if let sharePrice = stock.price {
        let totalCost = sharePrice * 100
        println(totalCost)
    }
}

函數的返回值是可選類型,我們通過可選綁定來檢測是否有值,顯然股票的價格也是一個可選類型,於是我們繼續使用if let 來檢測它是否有值。

上面的代碼沒有任何問題,不過這一層一層的 if 嵌套實在是太麻煩瞭,如果可選類型層次多點,很可能形成下面的情況:

if let x = xxx() {
    if let x = xxx() {
        if let x = xxx() {
            if let x = xxx() {
                if let x = xxx() {
                    if let x = xxx() {
                        if let x = xxx() {
                            if let x = xxx() {
                                if let x = xxx() {
                                    if let x = xxx() {
                                        if let x = xxx() {
                                            if let x = xxx() {

                                            }        
                                        }
                                    }
                                }
                            }   
                        }
                    }        
                }
            }
        }
    }   
}

呃上面這段代碼是我自己瞎寫的,原文中並沒有嗯。

除瞭使用 if let ,我們可以通過可選鏈來簡化代碼。我們可以用問號將多個可選類型串聯起來:

if let sharePrice = findStockCode(Apple)?.price {
    let totalCost = sharePrice * 100
    println(totalCost)
}

可選鏈提供瞭訪問變量的另一種方式,代碼現在看上去也更加的幹凈整潔。上面隻是一個基礎的使用,更加深入的學習可以參考官方文檔。

Swift 和 Objective-C 的交互

Swift 中的可選類型十分強大,盡管可能一開始的時候需要花點時間慢慢熟悉。可選類型讓你的代碼更清晰,而且可以避免一些 nil 引起的問題。

Swift 有設計與 Objective-C 交互的 API,當我們需要和 UIKit 或者其他框架的 API 交互的時候,你肯定會遇到可選類型。下面列舉一些 UITableView 中可能會遇到的可選類型:

func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
    // Return the number of sections.
    return 1
}

func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
    // Return the number of rows in the section.
    return recipes.count
}


func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell = tableView.dequeueReusableCellWithIdentifier(Cell, forIndexPath: indexPath) as UITableViewCell

    cell.textLabel.text = recipes[indexPath.row]

    return cell
}

總結

對於一名開發者來說,理解可選類型的工作原理是十分必要的。這也就是為什麼我們專門寫一篇文章來介紹可選類型。它可以幫助開發者在編譯階段就發現隱藏的問題,從而避免運行時錯誤。當你習慣瞭這種語法,你將會愈發欣賞可選類型的魅力所在。

享受這個美好的世界吧。真棒。(沒錯這句也是我亂加的)


 

發佈留言