如果看完 Swift 可選值詳解(上)后,你對(duì)可選值還是有些迷惑,甚至一頭霧水,那么我們來換一種方式來解釋。
看下面的方法:
func yearAlbumReleased(name: String) -> Int {
if name == "Taylor Swift" { return 2006 }
if name == "Fearless" { return 2008 }
if name == "Speak Now" { return 2010 }
if name == "Red" { return 2012 }
if name == "1989" { return 2014 }
return 0
}
該方法要求傳入一個(gè) Taylor Swift 專輯的名稱,返回對(duì)應(yīng)的發(fā)行年份。 但是如果我們傳入了專輯名稱“Lantern”,因?yàn)槲覀儗?Taylor Swift 與 Hudson Mohawke 混為一談(這是一個(gè)容易犯的錯(cuò)誤,對(duì)吧?),該方法就返回 0,因?yàn)樗皇?Taylor 的專輯之一。
但 0 在這里有意義嗎? 當(dāng)然,如果專輯是在公元 0 年發(fā)布的,當(dāng)時(shí)凱撒奧古斯是羅馬的皇帝,0 可能有意義,但在這里只會(huì)讓人感到困惑 —— 人們需要提前知道 0 的含義是“不被認(rèn)可”。(譯者注:這里說明的是,0 代表的含義需要提前定義,否者很容易被當(dāng)作年份處理)
最好是改寫為返回 int (有對(duì)應(yīng)年份時(shí))或 nil (沒有對(duì)應(yīng)年份),可選值使這一切變得容易。下面是改寫后的方法:
func yearAlbumReleased(name: String) -> Int? {
if name == "Taylor Swift" { return 2006 }
if name == "Fearless" { return 2008 }
if name == "Speak Now" { return 2010 }
if name == "Red" { return 2012 }
if name == "1989" { return 2014 }
return nil
}
由于用了可選值,我們需要用 if let
解包,因?yàn)樾枰獧z查值是否存在。
再來看另一種情形:
var items = ["James", "John", "Sally"]
現(xiàn)在我們想輸出其中一個(gè)名字對(duì)應(yīng)的索引值,我們可能這樣寫:
func position(of string: String, in array: [String]) -> Int {
for i in 0 ..< array.count {
if array[i] == string {
return i
}
}
return 0
}
其中循環(huán)檢查了數(shù)組成員,返回了對(duì)應(yīng)名稱的索引值,如果沒找到,返回 0 。
現(xiàn)在試著運(yùn)行下面 4 行代碼:
let jamesPosition = position(of: "James", in: items)
let johnPosition = position(of: "John", in: items)
let sallyPosition = position(of: "Sally", in: items)
let bobPosition = position(of: "Bob", in: items)
將輸出 0, 1, 2, 0 —— James 和 Bob 的位置是一樣的,然而他們實(shí)際上一個(gè)存在,而另一個(gè)并不存在。這是因?yàn)槲矣?0 表示 “不存在” 造成的。圖省事的話,可以用 -1 來表示不存在,但這樣一來,又必須時(shí)刻記得有這個(gè)特殊值意味著“不存在”。(譯者注:-1 引發(fā)的崩潰相信大家都有經(jīng)歷過,在 C 語言中,有符號(hào) -1 轉(zhuǎn)換為無符號(hào)時(shí),為無符號(hào)的最大值,從而導(dǎo)致很多混亂)
解決辦法還是可選值:用 nil 表示沒有找到對(duì)應(yīng)名稱。這也是數(shù)組內(nèi)置查找方法 someArray.firstIndex(of: someValue)
的實(shí)際做法。
當(dāng)你要對(duì)付“可能有可能沒有”的值,Swfit 強(qiáng)制要求在使用前解包,以此明確這里可能沒有值。if let
就是用來做這個(gè)的:如果有值就解包后使用,否者完全不使用它。Swift 根本不讓你憑空地使用一個(gè)可能為空的值。
強(qiáng)制解包
Swift 允許使用感嘆號(hào) !
來屏蔽它的安全性。當(dāng)你能確認(rèn)可選值有值時(shí),可以將感嘆號(hào)放在這個(gè)值后面來強(qiáng)制解包它。
請(qǐng)小心:如果你強(qiáng)制解包一個(gè)沒有值的可選值時(shí),會(huì)引發(fā)代碼崩潰。
下面是一些協(xié)同工作的代碼:
func yearAlbumReleased(name: String) -> Int? {
if name == "Taylor Swift" { return 2006 }
if name == "Fearless" { return 2008 }
if name == "Speak Now" { return 2010 }
if name == "Red" { return 2012 }
if name == "1989" { return 2014 }
return nil
}
var year = yearAlbumReleased(name: "Red")
if year == nil {
print("There was an error")
} else {
print("It was released in \(year)")
}
代碼獲得了專輯的發(fā)行年份。如果沒找到專輯,year
被置空,將打印出錯(cuò)信息。否則正確的發(fā)行年份會(huì)被打印。
是嗎?好吧,因?yàn)?yearAlbumReleased()
返回的是可選值,這里由沒有使用 if let
來解包。結(jié)果,實(shí)際輸出的是“It was released in Optional(2012)”——可能并不是我們想要的。
關(guān)鍵是,我們已經(jīng)檢查了可選值的有效性,再用 if let
進(jìn)行所謂的安全解包顯得有點(diǎn)無意義。因此,Swift 提供了解決辦法——把上面代碼中第二個(gè)print()
改為這樣:
print("It was released in \(year!)")
注意這個(gè)感嘆號(hào):它的意思是“我保證這里有值,強(qiáng)制解包吧”。
隱形解包
你還能用感嘆號(hào)來創(chuàng)建隱形解包可選值
,這是很多人真正感到困惑的地方。那么就好好讀讀下面的介紹。
- 靜態(tài)變量必須含有值。比如:String 必須含有 string ,哪怕是空字符串
""
,不能為 nil 。 - 可選值可以有值也可以沒有。使用前必須解包。比如
String?
可能為 string ,或者是 nil ,確定的辦法就是解包。 - 隱形解包可選值可以有值也可以沒有。但使用前不需要解包。Swift 不再為你檢查,所以你使用時(shí)要額外小心。比如
String!
可能為 string ,或者是 nil ——取決于你適當(dāng)?shù)氖褂盟?。它類似靜態(tài)變量,Swift 允許你直接訪問它的值,不需要安全地解包。如果你要這么做,意味著你知道一定有值,否者程序會(huì)崩。
使用隱形解包可選值
的主要情形是在使用 UIKit 的界面元素時(shí)(macOS 里對(duì)應(yīng) AppKit ),這些需要事先聲明,但是在創(chuàng)建之前你不能使用它們 —— 而 Apple 喜歡在最后一刻創(chuàng)建用戶界面元素以避免任何不必要的工作。 必須不斷地打開你肯定知道的值,這會(huì)使人厭煩,所以這些是隱式解開的。
不要擔(dān)心,如果您發(fā)現(xiàn)隱式解包的選項(xiàng)有點(diǎn)難以理解 - 當(dāng)您使用該語言時(shí),它將變得清晰。