對于寫慣了 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