optional 和非 optional 可以直接比較是否相等

大部分的時候,我們習慣將 optional 的內容讀取後再做相等(equal)的比較,例如以下例子:

var name: String? = "彼得潘"
if name! == "彼得潘" {
print("best name in the world")
}
if name != nil && name! == "彼得潘" {
print("best name in the world")
}
if let name = name, name == "彼得潘" {
print("best name in the world")
}

例子裡的字串可以比較,是因為 String 裡定義了以下 function。

public static func ==(lhs: String, rhs: String) -> Bool

其實用不著這麼麻煩,在剛剛的例子,我們可以將 optional 的變數 name 直接和字串”彼得潘” 比較,唯有當 name 不等於 nil,且 name 的內容為”彼得潘” 時,比較的結果才會為 true :

var name: String? = "彼得潘"
if name == "彼得潘" {
print("best name in the world")
}

為什麼 optional 的 name 可以和非 optional 的字串比較相等呢 ? 它們明明不同型,應該不能比呀。就好像彼得潘應該只能跟小王子比,不能跟小公主比呀。

真相就藏在定義 optional 的 enum Optional<Wrapped> : ExpressibleByNilLiteral 裡。enum Optional 裡定義了以下 function。

public func ==<T>(lhs: T?, rhs: T?) -> Bool where T : Equatable

從這個 function,我們了解 optional 可以跟 optional 比。但這並沒有解答我們的問題呀,為什麼 optional 可以跟非 optional 比 ? 別急,請耐心看完它上面的注解。

/// Returns a Boolean value indicating whether two optional instances are
/// equal.
///
/// Use this equal-to operator (`==`) to compare any two optional instances of
/// a type that conforms to the `Equatable` protocol. The comparison returns
/// `true` if both arguments are `nil` or if the two arguments wrap values
/// that are equal. Conversely, the comparison returns `false` if only one of
/// the arguments is `nil` or if the two arguments wrap values that are not
/// equal.
///
/// let group1 = [1, 2, 3, 4, 5]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first == group2.first {
/// print("The two groups start the same.")
/// }
/// // Prints "The two groups start the same."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In the following example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("23") // Optional(23)
/// if numberToFind == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// An instance that is expressed as a literal can also be used with this
/// operator. In the next example, an integer literal is compared with the
/// optional integer `numberFromString`. The literal `23` is inferred as an
/// `Int` instance and then wrapped as an optional before the comparison is
/// performed.
///
/// if 23 == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.

是不是覺得注解超長,我的字典裡只有放棄呀。好啦,簡單的說,注解裡提到,以下三種情況都可比較是否相等:

1 兩個變數(常數)都是 optional

2 一個變數(常數)是 optional,一個不是。

3 變數(常數)是 optional,另一個是 literal,比方數字 10,字串 “peter”。

後兩者可以比是因為非 optional 的變數(常數)或 literal 會先被包成 optional,然後再和 optional 的對手比較。因此,這時就可以比了,因為它們是同一型的人 ! 同樣都是 optional。

optional 既然可以比較相等,那它是否可以比大小呢 ? 很可惜的,它不能,因為它是膽小鬼,不敢比。在 enum Optional 裡,我們找不到 function > 或 < 的定義。

文章內容為彼得潘的 Swift 程式設計入門新增章節 ,18–10 optional 和非 optional 可以直接比較是否相等。

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com