幾周前 (譯者注:原文發表於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
,它有 code
和 price
兩個可選類型的屬性。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 中的可選類型十分強大,盡管可能一開始的時候需要花點時間慢慢熟悉。可選類型讓你的代碼更清晰,而且可以避免一些 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
}
對於一名開發者來說,理解可選類型的工作原理是十分必要的。這也就是為什麼我們專門寫一篇文章來介紹可選類型。它可以幫助開發者在編譯階段就發現隱藏的問題,從而避免運行時錯誤。當你習慣了這種語法,你將會愈發欣賞可選類型的魅力所在。
享受這個美好的世界吧。真棒。(沒錯這句也是我亂加的)