我們使用Swift這個蘋果新推出的編程語言已經有一段時間了。其中的一個極大的優點就是蘋果稱為“optional types”的東西。幾乎所有的objective-c程序員都知道用nil來表示某個引用類型的對象是沒有值的。但是要把nil和某個變量的類型聯系起來還是有些牽強。
這里,我們就來介紹一下Swift提供的optional type(可選類型)。先介紹一些實現的細節,然后指出optional type體系里的幾個要點。
類型?
在我們開始進入代碼前,先來看看為什么一個類型被定義為可選的。我們遇到的類型一般都是通常的,非可選的類型。包括一般的值類型,比如,Int,Bool和String。還有復雜點的引用類型,比如,UIView,等。
我們聲明這些類型的變量的時候,Swift要求必須給這些變量賦值。這一要求非常的嚴格,如果你想在初始化一個變量之前使用它使編譯不過的。
這個表面看起來很郁悶,但其實很有幫助。長遠來說,不讓這樣的代碼編譯通過,Swift可以避免發生因為使用了未初始化的值而引發的潛在的運行時錯誤。
let x: Int = nil
然而,有人會嘗試給let聲明的常量賦值為nil。
這樣的代碼會直接引發一個錯誤:類型“Int”不是protocol ’NilLiteralConvertible’類型。對於非optional type的類型,不實現“NilLiteralConvertible”protocol是不可以使用nil來初始化的。所以,簡單的x,只是一個Int型的實例,只能被賦值為Int的值而不是nil。
程序里的變量不可能全部都有初值,所以這個時候optional type就該出場了。Swift里,只要在任意類型的后面加一個問號以后就變成了optional type(可控類型)。比如,之前的例子中。只要給Int后面加一個問號就可以將x賦值為nil了。
這應該是很多寫Objective-C的哥們需要的了。變量值可以是“實際”的值,也可以是nil。這只取決於你的代碼處理的是哪種情況了。
裝箱
你可能會說,“Int只是值類型,不是一個對象怎么能使用nil呢?NSInteger是不能這么用得。。”
的確,你說的沒錯。NSInteger沒有nil值。或者更准確的說經過類型轉化后你會得到一個整型值0。所以,在Objective-C得API里定義了很多的標記表明“無值”狀態:0,1,NSIntergerMin,NSIntegerMax以及NSNotFound等,都是表明“nothing”的。
當你仔細考慮就會發現沒有一個一致的方式表明“nothing”這個值,而是用不同的自定義值標記“nothing”會增加一定的復雜度。取一個數組中不存在的值返回的事NSNotFound,取一個table view中不存在的row的時候返回的事一個-1。
Swift的optional type提供了一種更加清晰的表示方法。但是如何讓任何類型都具有optional type這個功能呢?這些都建立在泛型的基礎之上:
上面的就是Swift核心庫對於optional type的定義(稍微作了修改)。Swift定義了一個新的類型Optional,它由兩個值,一個是“nothing”,叫做None,一個是把某個類型T的值給包裝起來之后的值。Swift就是利用這一機制把某了類型的值包裝起來,當然這個值可以是空(nothing),也可以不是空。
在這個例子中, 第一個整數就只是一個Int型的值。后面的類型后面跟了問號“?”的其實都是Optional<T>類型的。或者,簡短的可以表示為Int?。
有了這個能力,Swift就可以給任何類型表示“nothing”的值nil,即使是Int。Optional type同時可以表示“real”的值,也可以表示“nothing”的值,而不需要其他的特殊的值。
拆箱
這樣表示optional value同時也會引發一個問題。現在我們知道optional type是一個獨立的類型:Optional<T>。所以,在需要T的地方,不如某個函數需要T類型的參數傳入,那么optional<T>的值是不能用的:
我們需要把需要的值從optional的箱子里拿出來。並且,很重要的一點,在那之前需要檢查這個值是否存在。Swift提供了嘆號“!”操作符來提取值。
記得把x的值修改為100,而不是之前的nil。因為,嘆號操作符只適用於optional type的值本身有“real”值的。如果沒有的話是會拋出運行時異常的。
所以,在拆箱取值以前,我們需要先判斷這個可選(optional)的值是否為空。就和我們在Objective-C中常做的類似。
但是如果這個x是從其他的方法返回回來的呢?我們可以直接調用這個函數來檢驗返回值, 完全不必要先給局部變量賦值,再檢測是否為空。
Swift已經實現了這個功能,叫做optional binding(可選綁定)。使用if和let兩個關鍵字就可以寫出一行緊湊的代碼來檢測函數返回值是否存在。
這里我們已經不用嘆號操作符來顯示的強制拆箱。這是optional binding(可選綁定)的另一個好用的地方。直接在if語句的判定表達式里拆箱optional type(可選類型),就可以確定這個optional type是否有值,不用手動的使用嘆號操作符來拆箱。
Chaining
現在我們建立一個准確的檢測和拆箱optional value(可選值)的規則。比如,如何在optional value上調用方法?你肯定會在一個可能為空的對象上調用方法,這是一定會發生的。在Objective-C中,在nil對象上調用方法會返回一個nil。
幸好Swift也可以做到這樣。使用optional chaining(可選鏈)的方式來調用可能為空(nil)的方法:
在對象和其調用的方法之間插入一個問號“?”操作符,我們就可以表明是要一個實際存在的值還是要一個“nothing”。這和Objective-C的調用非常類似。
注意:這樣調用的方法的返回值一定都是optional type(可選類型)的,即使這個方法的返回值被定義為非可選類型(non-optional type)。所以,在optional value(可選值)上調用的方法鏈上得任意一點的返回值都是optional的。在處理返回值的時候一定要考慮到值可能為空的可能。
考慮下面的代碼:
someMethod()方法的聲明中制定返回值為Int型,z還是得到一個optional value(可選值)。因為,我們使用了optional chaining(可選鏈)來調用方法。這可能看起來有點迷惑,但是很有幫助。尤其是在optional binding(可選綁定)的時候。比如,上面代碼中的if let z = y?.someMethod()表達式。
這樣可以很簡潔的處理一下問題:
- 如果y是nil(這里已經是nil),optional chaining(可選鏈)可以保證我們這樣寫代碼而不報錯
- 如果y是nil或者someMethod()方法返回nil,optional binding(可選綁定)不會把nil賦值給non-optional value(非可選值)z。
- 最終我們會得到z,但是不用手動拆箱。因為這是可選綁定的(optional binding)。
總之,對於處理nil值來說,Swift提供了一個非常清晰的系統。我們或得了額外的類型安全,避免了不必要的特殊定義的值,而且還是像Objective-C一樣簡潔。