今天突然想到一個問題,讓我覺得有必要總結一下switch語句。我們知道swift中的switch,遠比C語言只能比較整數強大得多,但問題來了,哪些型別可以放到switch中比較呢,物件可以比較麼?
官方文件對switch的用法給出了這樣的解釋:
Cases can match many different patterns, including interval matches, tuples, and casts to a specific type.
也就是說除了最常用的比較整數、字串等等之外,switch還可以用來匹配範圍、元組,轉化成某個特定型別等等。但文件裡這個including用的實在是無語,因為它沒有指明所有可以放在switch中比較的型別,文章開頭提出的問題依然沒有答案。
我們不妨動手試一下,用switch匹配物件:
class A {
}
var o = A()
var o1 = A()
var o2 = A()
switch o {
case o1:
print("it is o1")
case o2:
print("it is o2")
default:
print("not o1 or o2")
}
複製程式碼
果然,編譯器報錯了:“Expression pattern of type 'A' cannot match values of type 'A'”。至少我們目前還不明白“expression pattern”是什麼,怎麼型別A就不能匹配型別A了。
我們做一下改動,在case
語句後面加上let
:
switch o {
case let o1:
print("it is o1")
case let o2:
print("it is o2")
default:
print("not o1 or o2")
}
複製程式碼
OK,編譯執行,結果是:it is o1
。這是因為case let
不是匹配值,而是值繫結,也就是把o的值賦給臨時變數o1,這在o是可選型別時很有用,類似於if let
那樣的隱式解析可選型別。沒有打出it is o2
是因為swift中的switch,只匹配第一個相符的case,然後就結束了,即使不寫break
也不會跳到後面的case。
扯遠了,回到話題上來,既然新增let
不行,我們得想別的辦法。這時候不妨考慮一下switch
語句是怎麼實現的。據我個人猜測,估計類似於用了好多個if判斷有沒有匹配的case,那既然如此,我們給型別A過載一下==
運算子試試:
class A {}
func == (lhs: A, rhs: A) -> Bool { return true }
var o = A(); var o1 = A() ;var o2 = A()
switch o {
case o1:
print("it is o1")
case o2:
print("it is o2")
default:
print("not o1 or o2")
}
複製程式碼
很顯然,又失敗了。如果這就能搞定問題,那這篇文章也太水了。報錯資訊和之前一樣。可問題是我們已經過載了==
運算子,為什麼A型別還是不能餓匹配A型別呢,難道switch不用判斷兩個變數是否相等麼。
switch作為一個多條件匹配的語句,自然是要判斷變數是否相等的,不過它不是通過==
運算子判斷,而是通過~=
運算子。再來看一段官方文件的解釋:
An expression pattern represents the value of an expression. Expression patterns appear only in switch statement case labels.
以及這句話:
The expression represented by the expression pattern is compared with the value of an input expression using the Swift standard library ~= operator.
第一句解釋了之前的報錯,所謂的“express pattern”是指表示式的值,這個概念只在switch的case標籤中有。所以之前的報錯資訊是說:“o1這個表示式的值(還是o1)與傳入的引數o都是型別A的,但它們無法匹配”。至於為什麼不能匹配,答案在第二句話中,因為o1和o的匹配是通過呼叫標準庫中的~=
運算子完成的。
所以,只要把過載==
換成過載~=
就可以了。改動一個字元,別的都不用改,然後程式就可以執行了。Swift預設在~=
運算子中呼叫==
運算子,這也就是為什麼我們感覺不到匹配整數型別需要什麼額外處理。但對於自定義型別來說,不過載~=
運算子,就算你過載了==
也是沒用的。
除此以外,還有一種解決方法,那就是讓A型別實現Equatable
協議。這樣就不需要過載~=
運算子了。答案就在Swift的module的最後幾行:
@warn_unused_result
public func ~=<T : Equatable>(a: T, b: T) -> Bool
複製程式碼
Swift已經為所有實現了Equatable
協議的類過載了~=
運算子。雖然實現Equatable
協議只要求過載==
運算子,但如果你不顯式的註明遵守了Equatable
協議,swift是無法知道的。因此,如果你過載了==
運算子,就順手標註一下實現了Equatable
協議吧,這樣還有很多好處,比如SequenceType
的split
方法等。
最後總結一句:
能放在switch語句中的型別必須過載
~=
運算子,或者實現Equatable
協議。