Optional Type
假設我們有一個 Account
class,用來儲存使用者資料:
class Account {
var age: Int
...
}
當使用者註冊的時候,可以選擇不輸入年齡,所以當我們在存取 age
的時候,就會發生問題:
class Account {
var age: Int
...
}
var userAccount = Account();
...
println(userAccount.age)
// error: possibly missing value
age
有可能是 nil
。
Optional Type
因此 Swift 有一種特別的 type,叫做 optional type,用來解決這樣的問題。
什麼是 optional type? Optional type 表示:「這個東西有可能沒有值」。以 Swift 的角度來看,表示「這東西有可能為 nil
」。
在 Swift 中,?
為 keyword,用來表示 optional type ,?
為 Optional<Int>
的縮寫。Optional<Int>
則是被定義在 Swift standard library。所以,以下兩個定義的意思是一樣的:
var age: Int?
var age: Optional<Int>”
以上的程式碼定義了 age
是一個 option integer,不是單純的 「integer」 唷,是「optional integer」,也就是說,我們把 age
包裝 (wrap) 成 optional type。
如果你不給 age
初始值,age
會自動變成 nil
。
Non-Optional Type
當然啦,有 optional type,也會有 non-optional type。什麼是 non-optional type?就是平常你用的任何 type,都可以稱為 non-optional type:
var myString: String = nil
// compile-time error
var myObject: MyClass = nil
// compile-time error
以上程式碼在 compile-time 就會出錯。因為你沒有宣告這些 variable 是 optional 的。這是因為在 Swift 中,沒有辦法讓一個 non-optional type 的 variable 變成 nil
。
只要你沒宣告成 optional,這個 variable 就不會是 nil
。這就是 Swift 設計的重點之一:
讓程式更安全,讓程式是可以預測的,不用擔心 variable 會不會突然變成
nil
Optional Return Types
假設我們定義一個 function ,用來找出 array 物件的 index,有找到就回傳正確 index,沒找到就會回傳 nil
。因此,function 的 return type 也可以是 optional type:
func findIndexOfString(string: String, array: String[]) -> Int? {
for (index, value) in enumerate(array) {
if value == string {
return index
}
}
return nil
}
可以在 return type 的地方加上 ?
,變成 ->Int?
,就變成 optional return type。
Unwrapping Optionals
接著我們實際操作剛剛的 findIndexOfString()
:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index = findIndexOfString("Madison", neighbors)
其中 let index
會因為 type inference (註1) 變成 let index: Int?
,因此也可以寫成下面的樣子(可寫可不寫):
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index: Int? = findIndexOfString("Madison", neighbors)
當我們得到 index
,我們要去檢查是否為 nil
,然後輸出結果:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index = findIndexOfString("Madison", neighbors)
if index {
println("Hello, \(neighbors[index])")
// error: value of optional type 'Int?' not unwrapped
} else {
println("Must've moved away")
}
此時會發現 neighbors[index]
產生 compile-time error。 Why?
這是因為 index
已經被 包裝(wrap) 成 optional type,因此 index
有可能是 nil
。Compiler 不准讓有可能為 nil
的東西傳入 array 中。
因此我們要「強制」 unwrapping optional type。
只要在 optional type 的後面加上 !
,就表示 unwrapping,例如 neighbors[index!]
:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index = findIndexOfString("Madison", neighbors)
if index {
println("Hello, \(neighbors[index!])")
} else {
println("Must've moved away")
}
輸出結果為:
Hello, Madison
另外要注意,如果我們沒有用 if
先檢查 index
,而直接 unwrapping index
,也會產生錯誤:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index = findIndexOfString("Reagan", neighbors)
println("Hello, \(neighbors[index!])")
// runtime error!
Optional Binding
除了 !
的方法之外,還有另外一種 unwrapping 的方法,在 Modifying an Array and Dictionary 中有稍微提到過。這種方法叫做 optional binding ,利用 if let
:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
let index = findIndexOfString("Anna", neighbors)
if let indexValue = index {
println("Hello, \(neighbors[indexValue])")
} else {
println("Must've moved away")
}
if let indexValue = index {...
做兩件事:
index
是否為Int
? (有可能為nil
)- 如果為
Int
,則把index
unwrapping,並設為indexValue
的值。
所以可以看到最後的結果為 neighbors[indexValue]
,我們就不用寫 !
了,let
自動幫我們 unwrapping。
因此我們可以讓程式碼更簡潔:
var neighbors = ["Alex", "Anna", "Madison", "Dave"]
if let index = findIndexOfString("Anna", neighbors) {
println("Hello, \(neighbors[index])") // index is of type Int
} else {
println("Must've moved away")
}
輸出結果為:
Hello, Anna
小結:if let
optional binding 可以讓你同時 test and unwrap optional type。
Optional Chaining
之前都是一些簡單的範例,接下來我們用一個比較複雜的範例:
我們定義三個 class Person
, Residence
, Address
:
class Person {
var residence: Residence?
}
class Residence {
var address: Address?
}
class Address {
var buildingNumber: String?
var streetName: String?
var apartmentNumber: String?
}
它們的之間的關係如圖:
在 Person
中,如果是 Residence
才會有 Address
,有 Address
才會有 buildingNumber
, streetName
, apartmentNumber
這些欄位。
因此當我們要取得 buildingNumber
時,要逐一檢查:
var addressNumber: Int?
if let home = paul.residence {
if let postalAddress = home.address {
if let building = postalAddress.buildingNumber {
if let convertedNumber = building.toInt() {
addressNumber = convertedNumber
}
}
}
}
這樣寫起來很蠢,讓人看不懂,也很容易出錯。因此 Swift 提供了 optional chaining 的功能,讓我們可以用很簡單的方式去檢查每一個 optional type, optional chaining 如下:
var addressNumber: Int?
addressNumber = paul.residence?.address?.buildingNumber?.toInt()
這樣就可以把全部的檢查都串在一起,只要有任何一個 variable 為 nil
,就會回傳 nil
。
如果很順利,每個 variable 都不是 nil
,最後會呼叫到 toInt()
。
最後別忘了 toInt()
也要 unwrapping (因為 buildingNumber
是 optional type),最後的程式碼如下:
Combine with optional binding to unwrap
if let addressNumber = paul.residence?.address?.buildingNumber?.toInt() {
addToDatabase("Paul", addressNumber)
}
小結
以下是 WWDC 2014 有關 Optional Type 的結論:
Use optionals to safely work with possibly missing values
- Missing values are nil
- Present values are wrapped in an optional
Unwrap an optional to access its underlying value
- Use the forced-unwrapping operator (!) only if you are sure
- Use if let optional binding to test and unwrap at the same time
Optional chaining (
?
) is a concise way to work with chained optionals
大綱為以下 4 點:
- 在有可能發生 missing values (
nil
) 的情況下,要用 optionals 的方式操作,這樣比較安全。 這些有可能會 missing values 的東西會被包裝(wrap) 成 optional。
在使用 optional 的東西時,要記得 unwrap:
- forced-unwrapping operator (
!
) if let
optional binding
- forced-unwrapping operator (
如果要 unwrap 的東西是互相關連的,可以用 optional chaining (
?
) 讓程式碼更簡潔。
下一篇: Memory Management
註1: Type Inference 請參考:Variable And String