『Swift 5學習系列』-12. Optional
!(驚嘆號)還是?(問號)傻傻分不清楚,這是一開始接觸到Optional最容易遇到的問題,再不然就是!跟?在型別轉換之間的問題,搞得一個頭兩個大。所以下面整理了一些Optional的一些基本觀念,分享給有興趣了解Swift 5開發的新鮮人。
首先必須了解一下為什麼會有這個可有可無的Optional,在以安全做為訴求的Swift中,所有的變數在建立時就需要有值,如果在Swift中沒有Optional,那必須特別定義一些東西來表示變數的初始值的,而且這個定義必須要靠開發者維護,不但編譯器(Compile)無法幫忙檢查,時間久了也有可能不記得這個定義,而且在共同開發一個專案時,開發者之間也容易產生誤解。然而變數也有不知道要填什麼當初始值的時候,當然很多情況下變數有初始值,例如個人存款可能是0,也可能是-100,也有可能是100,所以這些都可以當作初始值給存款這變數,如var deposit = 0。
什麼時候會不知道變數的初始值呢?其實日常生活中就很多例子了,如今天有個聚會,有不認識的朋友,那如何給這些朋友一個初始值的稱呼呢?可以叫他們”unknow”。
這樣也是可以,但是會有一些問題產生,而且非常危險,所以不建議這樣使用的原因如下:
- 容易混淆:因為只有你知道”unknow”這個意思,但也有可能真的有人叫”unknow”,所以容易誤解。
- 修改時容易出錯:當真有人叫”unknow”時,那就需要修改另一個值當作初始值,這樣要十分小心地在所有程式碼中找尋修改,也可能將真的叫”unknow”的人,改成新的初始值。
因此變數還是有nil的需求, 所以需要使用Optional來解決這些不明確的問題。請記得只有Optional的變數或常數才能被設定為nil,如果將nil設定給不是Optional的變數或常數,則是會顯示錯誤的訊息。這邊要注意空字串(“”)不是nil,只有nil才是代表沒有內容。
宣告Optional
- Optional 是個包裝(wrapp)型別的容器,可以不給初始值,也可以給初始值。在型別後面加上?表示變數是個 Optional。切記問號需緊貼著型別,型別與?之間不可留空白。而問號原本的意思,就是代表有或沒有,因此完全說明了,它可能有值,也可能無值。
- Optional 預設值為nil,因此宣告後沒有給初始值,則自動設定為nil。
然而上面介紹的是用?方式宣告Optional,然而?的好朋友!,也可以宣告成Optional。差別在於!是自動取值 (Implicitly Unwrapped Optional),適合在大部分的情況都是有值的時候,如變數或常數一旦給值後,不會再變回無值的狀態,可以利用自動取值的方式宣告。像考試分數,一開始還沒考試之前分數是nil,一但考完之後一定會有值。
然而這邊就要非常注意一點,這非常重要,就是不能沒有給初始值就使用!,自動取值很方便,但在也使用上也有危險。若是 Optional 的變數或常數為nil,並未給值,只要取值時,就會造成程式Crash,那就只能說聲恭喜恭喜,你的程式會異常終止然後閃退。
設定、讀取Optional
到這邊你可能會有一個問題自動取值是什麼有什麼用?那就要回歸到如何設定、讀取Optional了。在設定Optional的方式與一般變數或常數設定一樣,並沒有差異。
然而最大的差異則在於讀取Optionl的變數或常數,在讀取Optional的變數或常數時,必須加上!(驚嘆號),來強制解開包裝(Force Unwrap)。在之前宣告時有提到Optional 是個包裝(wrapp)型別的容器,所以當需要取出來使用時需要解開包裝,而!(驚嘆號)就是用來解開包裝的。
如果沒有加上!(驚嘆號)來解開這Optional的變數或常數,則會產生錯誤。
所以這邊你就因該了解剛剛自動取值是什麼了,其實就是在讀取Optional變數或是常數時,不需要再加上!(驚嘆號),而可以直接讀取Optional變數或是常數。
簡單來說Optional就像是一個被鎖起來的存錢筒,當還沒放錢進去時就是存錢筒是空的(nil),錢放進去沒有什麼問題,但是要將錢拿出來時,就必須要有一把魔法鑰匙,這鑰匙就是!(驚嘆號),所以可以在宣告時使用,就像是說這次沒有鎖,可以隨便拿,也可以在讀取時使用,就如同拿魔法鑰匙開鎖,然後才能拿到錢。
接下來就要注意了,這是讀取Optional的變數或常數時,必須要檢查Optional的變數或常數是否有值。如果沒忘記前面有說過,若是 Optional 的變數或常數為nil,並未給值,只要取值時,就會造成程式Crash。所以在讀取前,必須要檢查是否為nil。方法有兩種,下面將分別介紹這兩種檢查方法:
方法1:利用if檢查是否有值,如果不是nil,再使用!(驚嘆號)讀取變數或常數。
方法2:利用Optional Binding的方式判斷取值。如用方法1,先判斷是否為nil,再用!(驚嘆號)取值方式,實在太麻煩,所以Swift提供了一種判斷取值技巧,將這兩步驟合而為一。而怎使用Optional Binding呢?
所以在if的範圍內,可以直接讀取if後的變數或常數,不需要再用!(驚嘆號)讀取原先Optional的變數或常數。這邊需要注意一點,就是範圍,因為是在if後宣告的變數或常數,所以這變數或常數的生命週期,就只有在這if所包含的大括號{}內。然而這樣還是很麻煩,因為還要想一個新的變數或是常數的名稱,所以Swift在Optional Binding時,可以讓新宣告的變數或常數與Optional的變數或常數名稱一樣。當然在大括號{}內的變數或常數,是新宣告的變數或常數,裡面存放的就是解開Optional後的變數或常數。
Optional無值時設定預設值
Optional的變數或常數若是沒有設定初始值,則會自動初始化為nil。然而有時希望在無值時依然有個預設值,Swift提供了一個方式,那就是“??”,在Optional的變數或常數後面加上??,然後再加上預設值就設定完成。當解開Optional的變數或常數為nil時,會則回傳預設值。
物件的Optional — Optional Chaining
前面有說的Optional的變數或常數在讀取時需要!(驚嘆號)來解開包裝,所以當Optional的是物件(Object)時,也是需要!(驚嘆號)來解開包裝,還且還需要層層判斷是不是nil。
而上面的方式比較繁雜,所以Swift提供了Optional Chaining這個方法,在使用Optional Chaining時,不需要完整的解開 Optional,並比對是否為nil,便可對 Optional 的物件讀取的property或是呼叫method。在取的property或是呼叫method的過程之中,遇到 Optional ,就直接忽略 Optional ,並執行下一個語法,直到語法全部完成,如過程中任何一個參數為 nil ,則馬上直接結束,並傳回 nil ,而且不會因為 nil 而 crash 。那如何寫呢?其實用法與 用!(驚嘆號)來解開包裝(Unwrap)很像,只是把!(驚嘆號)換成?(問號),就變成了Optional Chaining的方式讀取或是呼叫。有一點需要注意就是Optional Chaining的回傳結果也會是Optional。
最後補充一個可能會混淆的東西,就是Optional為什麼可以跟不是Optional比較是否相等,不是都要先讀取後才能做相等(Equal)的比較嗎?
只有三種情況可以比較是否相等:
- 比較的兩個變數或是常數都是Optional
- 一個變數或常數是Optional,另一個不是
- 一個變數或常數是Optional,另一個是literal,如數字100
但是只能比較是否相等,大於小於都不能比。