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 {... 做兩件事:

  1. index 是否為 Int? (有可能為 nil)
  2. 如果為 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?
}

它們的之間的關係如圖:

Screen Shot 2014-06-08 at 11.01.33 PM.png

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 typeoptional chaining 如下:

var addressNumber: Int?

addressNumber = paul.residence?.address?.buildingNumber?.toInt()

這樣就可以把全部的檢查都串在一起,只要有任何一個 variable 為 nil,就會回傳 nil


如果很順利,每個 variable 都不是 nil,最後會呼叫到 toInt()

最後別忘了 toInt() 也要 unwrapping (因為 buildingNumberoptional 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 點:

  1. 在有可能發生 missing values (nil) 的情況下,要用 optionals 的方式操作,這樣比較安全。
  2. 這些有可能會 missing values 的東西會被包裝(wrap) 成 optional。

  3. 在使用 optional 的東西時,要記得 unwrap:

    • forced-unwrapping operator (!)
    • if let optional binding
  4. 如果要 unwrap 的東西是互相關連的,可以用 optional chaining (?) 讓程式碼更簡潔。


下一篇: Memory Management


註1: Type Inference 請參考:Variable And String