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