理解 Swift 中的問號感嘆號

對于寫慣了 OC 代碼的程序員來說,不判空直接調用對象方法可能已經成為習慣了;而當方法的返回值是對象時,通常也是拿來就用。這些情況在 Swift 下都不存在了,因為 Swift 中出現了一個全新的概念:Optional(? & !)。

博客地址:http://davidleee.com
原文鏈接:http://davidleee.com/2017/07/14/Dive-in-Swift-Optional/

Optional 用于表示一種值可能為空的對象類型。一個 Optional 對象表示了兩種可能性:要么對象有值,你可以通過 “unwrap” 去獲取到這個值;要么對象里面沒有任何東西。

unwrap(解包):在對象后加 “?” 或 “!” 稱為將對象 “unwrap”,可以獲取到 Optional 里面的關聯值

Optional 這個概念在 C 語言或 Objective-C 里面并不存在。在 OC 中最接近的概念是:本來要返回對象的方法可能會返回 nil,這個 nil 表示“沒有有效的對象可以返回”;然而,這只在對象身上有效,它不能作用在結構體、基礎 C 類型或枚舉上。這些類型的變量如果沒有值,OC 會用 NSNotFound 來表示,它需要方法的調用者意識到這些特殊返回值的存在,并作出特殊的處理。

Optional 解決了上述問題,在 Swift 中,Optional 可以處理任何類型的空值,而不需要用一個特殊的常量去表示。

舉個栗子:
當我們需要將字符串轉換為數字時,在 Swift 中會使用 Int 的構造方法,如下:

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)

此時,convertedNumber 就是一個 Optional,看一下文檔可以發現,這個構造方法返回的是 Int?

原因是這個構造器可能會失?。?possibleNumber 也許并不能被轉化為數字。這里的 ? 表示返回的對象是一個可選值,它可能是某個 Int 類型的對象,也可能什么都沒有。(它不可能是別的類型的對象,因為 Swift 是強類型的)

nil

這里可以套用 OC 中的概念,nil 表示空值,但是在 Swift 中,它只能被賦值給 Optional 對象。當聲明一個 Optional 的變量又沒有給它賦值時,它會自動被賦值為 nil

var surveyAnswer: String?
// surveyAnswer == nil

本質上 Swift 的 nil 跟 OC 的 nil 是不一樣的。
在 OC 中,nil 是指向一個不存在對象的指針;在 Swift 中,nil 不是一個指針,它是一個帶有特定類型的表示數值缺失的值,任何類型的 Optional 都可以設置為 nil 而不只是對象類型。

If 和強制解包

可以使用 if 來判斷一個 Optional 對象是否有值,就像常見的判空操作。在判空后,這個 Optional 對象可以使用 ! 來強制解包,這相當于告訴編譯器:“我確定這個 Optional 對象肯定有值,直接取出來用吧!”

舉個栗子:

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}

! 被用在一個空值時,你的程序就會“卡蹦”一聲崩掉!

Optional Binding

這個機制可以用來判斷一個 Optional 對象是否有值,如果有值就將它復制給一個局部變量或常量,否則不執行任何操作。

我們用 Optional Binding 來改寫上一小節中的例子:

if let actualNumber = Int(possibleNumber) {
    print("\"\(possibleNumber)\" 是一個整型數字 \(actualNumber)")
} else {
    print("\"\(possibleNumber)\" 不能被轉化為整型")
}

Int() 返回的對象有值,這個值就會被直接賦給前面的 actualNumber ,所以這個變量就不是一個 Optional,可以不需要解包而直接使用了。

在這種用法下,if 原來的作用還是存在的,可以用逗號分隔不同類型的判斷,比如這樣:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 如果其中一個 Optional 沒有值,或者最后那個判斷的結果為 false,整個 if 判斷會直接返回 false

通過 Optional Binding 聲明的變量的作用于只在這個 if 之內,除非用 guard 去聲明,詳情參見官方文檔 Early Exit

隱式解包

有時候,在特定的代碼結構下,一個 Optional 對象可以被確保永遠都有值(或者說理應永遠都有值)。這種時候,每次使用這個對象都進行判空和解包就顯得非常多余了,于是我們可以在聲明這個對象的時候用隱式解包來處理:

let forcedString: String!
print(forcedString) // 不需要寫成 “forcedString!”

事實上,例子中的 forcedString 還是一個 Optional 沒變,但是我們讓它在使用的時候自動解包,不需要我們手動加 ! 了。

鏈式調用

如果我們要取得的對象被包裹在了一層又一層的 Optional 之中,取得它的過程可能非常繁瑣:

var label: UILabel?
if label != nil {
    if let temp1 = label.text {
        if let temp2 = temp1.hashText {
            ...
        }
    }
}

這時候,可以使用鏈式調用的方式改寫:

if let hashText = label?.text?.hashText {
    ...
}

在這句話當中,? 表達的意思是:“如果這個對象有值就取出來,繼續下面的步驟;如果沒有值,就當我沒寫過這句話吧”。

默認值

在一些情況下,我們會想要 Optional 對象為 nil 的時候給出一個默認值。比如我們使用一個 String?label.text 賦值時,我們并不希望設置一個 nil 上去,因為那會讓 UILabel 的高度變為0。
一種很簡便的寫法是這樣的:

let s: String?
s = ...
label.text = s ?? "placeholder"

這樣,當 s 為空時,label.text 的值就會是 “placeholder”。

總結

Swift 中的 Optional 其實是一個 enum:

enum Optional<T> {
    case none
    case some(T)
}

而它現在所見到的使用方法都可以認為是 Swift 的語法糖:

let x: String?
// 等價于
let x = Optional<String>.none

let x: String? = "hello"
// 等價于
let x = Optional<String>.some("hello")

let y = x!
// 等價于
switch(x) {
    case .some(let value): y = value
    case .none: // 拋個異常并整死你的應用:)
}

if let y = x {
    y.doSomething()
}
// 等價于
switch(x) {
    case .some(let y):  y.doSomething()
    case .none: break
}

什么?你想問這是哪門子的 enum?推薦你去看看官方文檔 Enumeration

參考資料

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

推薦閱讀更多精彩內容

  • 01-常量與變量 學習swift第一步打印Hello World print("Hello World") swi...
    iOS_恒仔閱讀 5,219評論 2 19
  • Swift 介紹 簡介 Swift 語言由蘋果公司在 2014 年推出,用來撰寫 OS X 和 iOS 應用程序 ...
    大L君閱讀 3,304評論 3 25
  • 對各種值為"空"的情況處理不當,幾乎是所有Bug的來源。 在我們的例子里,盡管tmp的值是nil,但調用tmp的r...
    AKyS佐毅閱讀 10,565評論 1 13
  • Swift 簡介 查看Swift當前版本 簡介 Swift 語言由蘋果公司在 2014 年推出,用來撰寫 OS X...
    mian小爬閱讀 359評論 0 1
  • 常量與變量使用let來聲明常量,使用var來聲明變量。聲明的同時賦值的話,編譯器會自動推斷類型。值永遠不會被隱式轉...
    莫_名閱讀 466評論 0 1