2014/9/26

Swift Optional 變數的處理方法

在學習 Swift 語法的時候,我遇到最大的障礙是 Optional(!?)。驚嘆號跟問號和型別轉換的問題,這兩個符號寫在變數型別上時,定義了變數是否可以為空值(nil),以及變數預設為空值(?)或預設為有值(!)。在語言設計上,我們可以知道 Apple 會這樣設計,目的是為了寫程式的人必須處理當變數為空值時的情形,這是為了程式的安全性。

在變數宣告上寫作

var s:String = "s"
var s0:String!
var s1:String! = "s1"
var ss0:String?
var ss1:String? = "ss1"

其中s不可為空值,s0, s1, ss0, ss1 可為空值,s0, s1 預設不為空值,ss0, ss1 預設為空值。
在變數使用上,假設我們現在要做的事情是取得字串長度,預設為空值的變數必須經過一個轉換才能使用,基本的用法如下:


1. 用 lf let 解開變數封裝
var stringLength1:Int = 0
if let ss0 = ss0 {
    //如果ss0有值會進入這裡,且ss0的型別被改為String
    stringLength1 = ss0.utf16Count
}
else
{
    //如果是空值會進入這裡
}


2. 用問號,當 ss0 是 nil 時 return nil ,所以等號左邊必須也用可為空值的變數去接
var stringLength2:Int!
stringLength2 = ss0?.utf16Count


3. 用驚嘆號強制解開變數封裝,這種寫法當 ss0 是 nil 時程式就 crash
var stringLength3:Int
stringLength3 = ss0!.utf16Count

方法 1 清楚地描述了 ss0 是空值或不是空值分別該怎麼做。
方法 2 是說當 ss0 是空值時,stringLength2 就設為空值。
方法 3 則是當 ss0 是空值時,程式就要 crash。

方法 1 或方法 2 都有處理當 ss0 為 nil 時該怎麼做,而方法 3 沒有,所以方法 3 是危險的。
也許我們可以當 ss0 為空值時給 ss0 一個預設值,讓程式繼續執行下去,做法如下:
if ss0 == nil {
   ss0 = "nil" 
}


ss0 = ss0 == nil ? "nil" : ss0

我們把設定預設值的動作寫成函數,以便日後使用。
public func unwarp(string:String?) -> String{
    return string == nil ? "nil" : string!
}
unwarp(ss0)
// return "nil"
unwarp(ss1)
// return "ss1"

我們也可以也把它寫成泛型,並且加入預設值的設定。
public func unwarp<T>(value:T?, defaultValue:T) -> T{
    return value == nil ? defaultValue : value!
}
unwarp(ss0, "XDDD")
// return "XDDD"
unwarp(ss1, "XDDD")
// return "ss1"

於是我們可以對所有型別都做出 unwarp function。


***  2014/10/9 更新  ***
上述的泛型 unwarp 函數在輸入的兩個變數型別不同時會以 value 為主
以下的 code 修正這個問題,改為以 defaultValue 的型別為主

public func unwarp(value:AnyObject?, defaultValue:T)->T{
    if let value: AnyObject = value {
        return value is T ? value as T : defaultValue
    }else{
        return defaultValue
    }
}
unwarp("1.0",0.0)
// return 0.0
unwarp(1.0,0.0)
// return 1.0
unwarp(nil,0.0)
// return 0.0
var a1:Int = unwarp(3.5,1.5)
// a1 = 3
var a2:Int = unwarp("3.5",1.5)
// a2 = 1


***  2015/2/12 更新  ***
發現內建處理解開封印時加入預設值的運算子 ??

var ss:String?
ss ?? "QQ"
// return QQ