[Swift]Swift中的Optional型別

  • 8266
  • 0
  • 2016-03-05

摘要:[Swift]Swift中的Optional型別

Optional概念

這是Swift因為安全性考量而設計的,是為了避免在Run-Time時發生的一些常見錯誤。因為不同型別與nil進行運算,例如加減,會出現Run-Time時問題。所以使用Optional,會在編譯時期就觸發錯誤,以早先發現nil的型別運算問題。

宣告Optional變數

Optional的變數是指該變數允許為nil(就是C#中的null),與C#中的nullable 是相同的概念。也就是當宣告變數為Optional後,該變數除了可以設定該型別的值之外,還可以設定為nil

宣告Optional的語法是,在宣告型別後加上問號?運算子。這樣子在宣告變數的型別之後,即使不給予值,就會自動指派nil給該變數。

var a:String = "Hello "
var b:String?

設定預設值

??運算子是用來判斷該運算子前的算式為nil時,就給予一個預設值。如下例

func checkAge(name:String)-> Int?{
    if(name == "aaa"){
        return 20;
    }else{
        return nil;
    }
}

var memberAge = checkAge("bbb") ?? 30

使用Optional變數

Unwrap Optional變數

P01

上圖顯示的錯誤訊息是說b變數需要被unwrap後才能與a變數進行運算,概念上是string型別被包裝在Optional型別中,解開包裝(Unwrap)後才是其可以運作的真正型別。那麼要怎麼解開一個Optional型別的變數呢?就是使用!運算子。

var a:String = "Hello "
var b:String? = "aaa"

var c = a+b!

這就是所謂的強迫解除(forced unwrapping)。這是讓開發者自行確認該變數為非nil,所以不進行編譯期檢查。

但如果該變數實際上還是nil值,則在Run-Time時進行運算就會發生錯誤。 P02

Optional Binding - if let & if var

如果無法確定變數是否為nil就貿然使用! 運算子,當該變數真的為nil時,就會發生Run-Time Error。所以當在執行Optional型別的運算的時候,最好是先判斷是否為nil值再進行運算。

var a:String = "Hello "
var b:String? = "bbb"

if b != nil{
    var c = b!
    print(a+c)
}

上面的寫法其實有些麻煩,所以Swift提供了一個語法 - 使用if letif var。如下例所示,意思是如果b變數不是nil,則c變數等於b變數,並執行後面的statement。如果b變數nil,則不執行該段statement。if letif var就是所謂的Optional Binding語法

var a:String = "Hello "
var b:String? = "bbb"

if let c = b{
    print(a+c)
}

Optional Chaining

當需要存取一個Optional物件中的一個變數時,使用Optional Binding的語法檢查是否為nil,不為nil時才繼續取得該變數。這種方式很直覺,但是如果該變數又是Optional物件時,則又需要使用Optional Binding的語法檢查。

這種多層的Optional物件的檢查,會造成巢狀的結構。同時,最後取得的變數就只能在{}中存活。要使用它還需要另外包一個變數傳出去。所以會有Optional Chaining的語法?.,這語法讓程式碼變得更簡潔。

class Person{
    var myHouse: House?
}

class House{
    var myRooms: Rooms?
}

class Rooms{
    var numberOfDoors = 1
}

var foo = Person()
var num = foo.myHouse!.myRooms!.numberOfDoors

例如上面這個例子,foo.myHouseOptoinal型別。如果想取得該物件底下的變數,最簡單的方式是透過!直接unwrap。但這種做法在Optoinal物件是nil時,會導致Run-Time Error - fatal error: unexpectedly found nil while unwrapping an Optional value

透過Optional Binding語法,會有這種的巢狀結構

var foo = Person()
if let h = foo.myHouse{
    if let r = h.myRooms{
        print(r.numberOfDoors)
    }
}

對比之下,Optional Chaining的語法?. 讓程式碼變得更簡潔

var num = foo.myHouse?.myRooms?.numberOfDoors

上例透過Optional Chaining回傳的是Int?的型別,只要透過Optional Chaining的方式取得的型別,都會是Optional型別。Optional Chaining應該算是一個運算式,他處理了是否為nil的判斷,並依據實際的物件內容回傳nil或物件值。因為可能回傳nil或實際物件型別,所以這等於是把型別包裝成Optional。

既然是Optional型別,所以也就可以使用Optional Binding語法做是否為nil的判斷

var foo = Person()
if let num = foo.myHouse?.myRooms?.numberOfDoors{
    print("可取值 \(num)")
} else {
    print("不可取值-nil")
}

Implicitly Unwrapped Optionals

Implicitly Unwrapped Optionals的語法是在宣告時,加上一個驚嘆號!。會有這個型別出現是因為在某些狀況之下,宣告變數初始時會是nil,可是當實際存取時又確定一定會有值。

底下這個連結 - Uses for Implicitly Unwrapped Optionals in Swift 列出幾個需要Implicitly Unwrapped Optionals的理由:

  • 當使用MVC的Controller中的@IBOutlet物件時,初始化的時候會是nil,但是執行時會連結到View的物件
  • 在Swift中使用Objective-C API的物件時,其型別是Implicitly Unwrapped Optionals。因為Objective-C物件都是指標,也就代表可能為nil

在這情況之下,每次要取用時,都需要Unwrap,其實是滿多餘的動作。可是又不能宣告成一個非Optional的變數,因為他的確一開始的時候會是nil。所以宣告成Implicitly Unwrapped Optional,取值時不需要進行Unwrap,就可以直接使用。

var name:String! = "boo"
print("Hello \(name)")