在我們進行 App 開發的時候,經常會用到的一個操作就是判斷兩個對象是否相等。比如兩個字符串是否相等。而所謂的 相等 有着兩層含義。一個是值相等,還有一個是引用相等。如果熟悉 Objective-C 開發的話,就會知道 Objective-C 為我們提供了一系列 isEqual:
方法來判斷值相等,而 == 等於號用來判斷引用相等。 我們來看一個 Objective-C 的例子就會更加明白了:
NSArray *arr1 = @[@"cat",@"hat",@"app"];
NSArray *arr2 = @[@"cat",@"hat",@"app"];
NSLog(@"result %i", (arr1 == arr2)); // result 0
NSLog(@"result %i", [arr1 isEqualToArray:arr2]); // result 1
上面的代碼中,我們定義了兩個數組,arr1 和 arr2 這兩個數組中保存的是同樣的元素。接下來我們對他們進行相等比較並輸出結果。第一次我們用 == 等於號進行比較,返回的結果是 0, 也就是說比較失敗了。原因相信有些經驗的同學都應該明白,是因為 在 Objective-C 中 == 比較的是引用,因為 arr1 和 arr2 是兩個不同的對象,所以即便他們的值相同,但他們的引用也不相同。所以 == 比較會失敗。而第二個比較用得是 NSArray 的 isEqualToArray 方法,這個方法是用來進行值比較的,因為這兩個數組的值相同,所以 isEqualToArray 的比較方法會返回 1。
<!-- more -->
Equatable 協議
我們通過 Objective-C 了解了相等操作的運作機制,包含了值相等和引用相等。現在我們回到 Swift 中來。相信有 Objective-C 經驗的同學一定會在找 isEqual 方法,但會發現 Swift 的原生類中沒有 isEqual 的定義。
如果想對 isEqual 進行更深入的了解,這篇文章 Equality 里面有詳細的講解。我們這里不過多敘述。
Swift 中沒有提供 iEqual 方法的定義,而是用另一種更加自然的方式,重載操作符 來處理相等判斷的。重載 ==
操作符是通過 Equatable 協議來完成的。我們通過實現這個協議中的方法來實現操作符的重載。
func ==(lhs: Self, rhs: Self) -> Bool
Swift 中自己實現了數組類,叫做 Array
, 關於 Array
類型,這篇文章有詳細的敘述 Swift Tips - Array 類型。Swift 中的 Array
類型實現了 Equatable
協議,所以我們可以對 Array
類型的實例通過 ==
操作符進行比較:
var arr1 = ["cat","hat","app"]
var arr2 = ["cat","hat","app"]
println arr1 == arr2 // true
我們看到,在 Swift 中直接使用 ==
進行比較的效果和 Objective-C 中的 isEqual:
方法的實際效果是一樣的。他們都對值進行比較。Swift 的這點特性和 Objective-C 略有不同。Swift 的原生類中,幾乎都實現了 Equatable
協議。
實現 Equatable 協議
我們現在了解了 Swift 中的 Equatable
協議。相信細心的同學已經發現了,我們在 Swift 中之所以可以對實例用 ==
進行比較,是因為這個符號操作的對象實現了 Equatable
協議。那如果對沒實現這個協議的對象使用 ==
進行比較呢?我們可以看一下這段代碼:
上面的代碼中,我們定義了一個 Name
類,並實例化了這個類的兩個對象 john
和 copy
。然后我們用 ==
對這兩個進行比較,這時編譯器報錯了。原因就是我們這個類沒有實現 Equatable
協議。
那么下面我們對這個類進行一下修改:
class Name :Equatable {
var firstName:String?
var lastName:String?
init(firstName first:String, lastName last:String){
self.firstName = first
self.lastName = last
}
}
func ==(lhs: Name, rhs: Name) -> Bool {
return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}
在我們的修改中,我們實現了 Equatable
協議,並實現了 func ==(lhs: Name, rhs: Name) -> Bool
方法。判斷只有在 firstName
和 lastName
都相等的情況下,這兩個 Name 對象才算相等。
進行完這個修改后,我們就可以繼續使用我們之前的判斷了:
if john == copy {
print("equal") //equal
}
這次,if
語句中的 print
方法被成功的執行了。
現在我們知道了 Swift 中的大部分類都有自己對 ==
操作符的實現。那么如果我們現在想比較兩個對象的引用是否相等該怎么辦呢?
我們可以使用 ObjectIdentifier
類來取得對象的引用標識,我們明確的調用這個類的構造方法來取得對象的引用,並進行比較:
if ObjectIdentifier(john) == ObjectIdentifier(copy) {
print("equal")
}else{
print("not equal") // not equal
}
我們用 ObjectIdentifier
取得了對象的引用地址,並且 ObjectIdentifier
本身又實現了 Equatable
協議,所以 我們對轉換后的 ObjectIdentifier
對象用 ==
進行比較,就可以判斷應用是否相同了。
Comparable
我們剛才介紹了 Equatable
協議,和它相關的還有一個 Comparable
協議。 Equatable
協議是對相等性進行比較。而 Comparable
是對順序進行比較。比如 Swift 中的 Double
類,就實現了 Comparable
協議:
var left:Double = 30.23
var right:Double = 50.55
if left < right {
print("30.23 is less than 50.55")
}
實現了 Comparable
協議的類,就可以使用 >,<,<=,>=
這些操作符進行比較了。這個協議的定義如下:
protocol Comparable { ... }
func <=(lhs: Self, rhs: Self) -> Bool func >=(lhs: Self, rhs: Self) -> Bool func >(lhs: Self, rhs: Self) -> Bool
你是不是會覺得,要實現比較操作,我們要實現這里面所有的方法呢。我們不妨花幾分鍾時間仔細的看一看這個協議的定義,並且思考一下。
- 首先,順序比較的操作符有四個,
<=
,>=
,>
,< 大家可以看一下這個協議,好像是不是少了 < 的定義呢? - 然后,我們再分析一下這些操作符的邏輯關系,
>=
, 實際上是>
和==
的一個並列關系,如果我們實現了這兩個操作符,實際上>=
的邏輯也就明確了。這個>=
的操作符的邏輯一般就是這個語句:return lhs > rhs || lhs == rhs
。
通過上面簡短的分析,我們是不是發現了一個規律? 其實就是,我們只要實現某幾個操作符,就能推出其他操作符的邏輯。舉個例子,以上面的 Comparable
接口的定義來看,<=
和 >
這兩個操作符,我們只需要實現其中一個就可以推導出另外一個。比如,我們實現了 >
操作符,那么 '<=' 操作符只需要的前面那個函數取反即可。
那么,我們 <=
函數的實現,只需要類似這樣實現即可:
func <=(lhs: Self, rhs: Self) -> Bool {
return !(lhs > rhs)
}
仔細想一想,其實我們只要實現 ==
和 <
操作符,其他的幾個操作符都能夠通過這兩個推導出來了:
- >= 可以通過
<
的邏輯取反和==
一起的邏輯或操作推導出。 - > 可以通過
<
的邏輯取反推導出。 - <= 可以通過
<
和==
的邏輯或操作推導出。
關於這方面的知識,有一個概念叫做 嚴格全序,有興趣的同學可以讀一讀。
那么現在問題又來了,我們其實只要實現 ==
和 <
方法的邏輯就可以完成比較操作了,那么對其他操作符的實現代碼,其實都是一樣的。就顯得有些冗余了。
而 Swift 正為我們解決了這個問題,為我們提供了協議的默認實現,仔細查看 Swift 文檔的話,我們會發現除了 Comparable
協議,還定義了一個 _Comparable
協議,而前者繼承自后者。 _Comparable
協議的定義如下:
protocol _Comparable { ... }
func <(lhs: Self, rhs: Self) -> Bool
我們發現,剛剛我們丟失的 <
在這里找到了。 _Comparable
就是 Swift 中提供的默認協議實現機制的例子。_Comparable
協議中提供了對其他幾個操作符 >=,>,<=
的默認實現。
由於 Comparable
同時繼承自 _Comparable
和 Equatable
協議,所以我們只需要實現 <
和 ==
這兩個操作符,即可完成整個比較操作的實現。下面我們來看一個例子:
class Name: Comparable {
var firstName:String
var lastName:String
init(firstName first:String,lastName last:String){
self.firstName = first
self.lastName = last
}
}
func ==(lhs: Name, rhs: Name) -> Bool {
return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName
}
func <(lhs: Name, rhs: Name) -> Bool {
return lhs.firstName < rhs.firstName && lhs.lastName < rhs.lastName
}
let john = Name(firstName: "John", lastName: "Smith")
let johnClone = Name(firstName: "John", lastName: "Smith")
let jack = Name(firstName: "Jack", lastName: "Smith")
let mary = Name(firstName: "Mary", lastName: "Williams")
print(john >= jack) //true
print(john <= jack) //true
print(john == jack) //false
print(john > jack) //false
print(john < jack) //false
print(jack < mary) //true
print(john == johnClone) //true
var names:Array<Name> = [johnClone,mary,john,jack]
//[{firstName "John" lastName "Smith"}, {firstName "Mary" lastName "Williams"}, {firstName "John" lastName "Smith"}, {firstName "Jack" lastName "Smith"}]
names.sort { (lhs, rhs) -> Bool in
return lhs > rhs
}
//[{firstName "Mary" lastName "Williams"}, {firstName "John" lastName "Smith"}, {firstName "John" lastName "Smith"}, {firstName "Jack" lastName "Smith"}]
我們在這里完整的實現了 Comparable
協議,我們定義了一個 Name
類,並實現了 <
和 ==
協議方法,其中一個來自 _Comparable
協議,一個來自 Equatable
協議。而 Comparable
協議中的三個比較方法,已經在 _Comparable
中提供了默認的實現,所以我們就不用再自己實現一遍了。(注意兩個 Comparable 和 _Comparable 的下划線區別,這是兩個不同的協議。)
Hashable
最后一個要提到的還有 Hashable 協議。我們對 Dictionary 的概念應該不陌生,那么如果一個對象想作為 Dictionary 的 key 的話,那么就需要實現 Hashable 協議。
Swift 中以下這些類默認實現了 Hashable 協議:
- Double
- Float, Float80
- Int, Int8, Int16, Int32, Int64
- UInt, UInt8, UInt16, UInt32, UInt64
- String
- UnicodeScalar
- ObjectIdentifier
所以這些類的實例,可以作為 Dictionary 的 key。 我們來看一下 Hashable 協議的定義:
protocol Hashable { ... }
var hashValue: Int { get }
定義非常簡單,我們只需要實現 hashValue
定義 hash 值的計算方法即可。關於 hash 值的優化方法我們就又是另外一個課題了,所以我們這里只做簡單討論,將用類的屬性的 hash 值再次進行異或操作計算新的 hash 值即可。還是以我們的 Name 類為例:
class Name: Comparable,Hashable {
...
var hashValue: Int {
return self.firstName.hashValue ^ self.lastName.hashValue
}
}
接下來我們就將它作為字典的 key 了:
let john = Name(firstName: "John", lastName: "Smith")
let johnClone = Name(firstName: "John", lastName: "Smith")
let jack = Name(firstName: "Jack", lastName: "Smith")
let mary = Name(firstName: "Mary", lastName: "Williams")
var nameMap:[Name: String] = [:]
nameMap[john] = "Manager"
nameMap[jack] = "Stuff"
這片文章介紹了 Swift 中的三個協議 Equatable
,Comparable
,Hashable
,雖然我們平時不會太注意這幾個協議,但在我們的開發工作中,他們卻無時無刻不再起着作用,我們的 if 語句,排序操作,字典和數組的操作,都和這幾個協議相關。 而 Swift 中的大部分類,基本都實現了這幾個協議。可以說雖然我們經常遺忘他們,但它們又無時無刻不在我們左右。了解他們之后,一定會對我們大有幫助。
更多文章請訪問: www.theswiftworld.com
更多好文,掃碼關注微信公眾號: