Swift Explore - 關於 Swift 中的 isEqual 的一點探索

 

在咱們進行 App 開發的時候,常常會用到的一個操做就是判斷兩個對象是否相等。好比兩個字符串是否相等。而所謂的 相等 有着兩層含義。一個是值相等,還有一個是引用相等。若是熟悉 Objective-C 開發的話,就會知道 Objective-C 爲咱們提供了一系列 isEqual: 方法來判斷值相等,而 == 等於號用來判斷引用相等。 咱們來看一個 Objective-C 的例子就會更加明白了:php

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

上面的代碼中,咱們定義了兩個數組,arr1arr2 這兩個數組中保存的是一樣的元素。接下來咱們對他們進行相等比較並輸出結果。第一次咱們用 == 等於號進行比較,返回的結果是 0, 也就是說比較失敗了。緣由相信有些經驗的同窗都應該明白,是由於 在 Objective-C 中 == 比較的是引用,由於 arr1arr2 是兩個不一樣的對象,因此即使他們的值相同,但他們的引用也不相同。因此 == 比較會失敗。而第二個比較用得是 NSArray 的 isEqualToArray 方法,這個方法是用來進行值比較的,由於這兩個數組的值相同,因此 isEqualToArray 的比較方法會返回 1。css

<!-- more -->html

Equatable 協議

咱們經過 Objective-C 瞭解了相等操做的運做機制,包含了值相等和引用相等。如今咱們回到 Swift 中來。相信有 Objective-C 經驗的同窗必定會在找 isEqual 方法,但會發現 Swift 的原生類中沒有 isEqual 的定義。java

若是想對 isEqual 進行更深刻的瞭解,這篇文章 Equality 裏面有詳細的講解。咱們這裏不過多敘述。nginx

Swift 中沒有提供 iEqual 方法的定義,而是用另外一種更加天然的方式,重載操做符 來處理相等判斷的。重載 == 操做符是經過 Equatable 協議來完成的。咱們經過實現這個協議中的方法來實現操做符的重載。web

func ==(lhs: Self, rhs: Self) -> Bool

Swift 中本身實現了數組類,叫作 Array, 關於 Array 類型,這篇文章有詳細的敘述 Swift Tips - Array 類型。Swift 中的 Array 類型實現了 Equatable 協議,因此咱們能夠對 Array 類型的實例經過 == 操做符進行比較:canvas

var arr1 = ["cat","hat","app"]
var arr2 = ["cat","hat","app"]
println arr1 == arr2        // true

咱們看到,在 Swift 中直接使用 == 進行比較的效果和 Objective-C 中的 isEqual: 方法的實際效果是同樣的。他們都對值進行比較。Swift 的這點特性和 Objective-C 略有不一樣。Swift 的原生類中,幾乎都實現了 Equatable 協議。swift

實現 Equatable 協議

咱們如今瞭解了 Swift 中的 Equatable 協議。相信細心的同窗已經發現了,咱們在 Swift 中之因此能夠對實例用 == 進行比較,是由於這個符號操做的對象實現了 Equatable 協議。那若是對沒實現這個協議的對象使用 == 進行比較呢?咱們能夠看一下這段代碼:數組

上面的代碼中,咱們定義了一個 Name 類,並實例化了這個類的兩個對象 johncopy。而後咱們用 == 對這兩個進行比較,這時編譯器報錯了。緣由就是咱們這個類沒有實現 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 方法。判斷只有在 firstNamelastName 都相等的狀況下,這兩個 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

你是否是會以爲,要實現比較操做,咱們要實現這裏面全部的方法呢。咱們不妨花幾分鐘時間仔細的看一看這個協議的定義,而且思考一下。

  1. 首先,順序比較的操做符有四個,<=,>=,>,< 你們能夠看一下這個協議,好像是否是少了 < 的定義呢?
  2. 而後,咱們再分析一下這些操做符的邏輯關係,>=, 其實是 >== 的一個並列關係,若是咱們實現了這兩個操做符,實際上 >= 的邏輯也就明確了。這個 >= 的操做符的邏輯通常就是這個語句: return lhs > rhs || lhs == rhs

經過上面簡短的分析,咱們是否是發現了一個規律? 其實就是,咱們只要實現某幾個操做符,就能推出其餘操做符的邏輯。舉個例子,以上面的 Comparable 接口的定義來看,<=> 這兩個操做符,咱們只須要實現其中一個就能夠推導出另一個。好比,咱們實現了 > 操做符,那麼 '<=' 操做符只須要的前面那個函數取反便可。

那麼,咱們 <= 函數的實現,只須要相似這樣實現便可:

func <=(lhs: Self, rhs: Self) -> Bool {

  return !(lhs > rhs)

}

仔細想想,其實咱們只要實現 ==< 操做符,其餘的幾個操做符都可以經過這兩個推導出來了:

  1. >= 能夠經過 < 的邏輯取反和 == 一塊兒的邏輯或操做推導出。
  2. > 能夠經過 < 的邏輯取反推導出。
  3. <= 能夠經過 <== 的邏輯或操做推導出。

關於這方面的知識,有一個概念叫作 嚴格全序,有興趣的同窗能夠讀一讀。

那麼如今問題又來了,咱們其實只要實現 ==< 方法的邏輯就能夠完成比較操做了,那麼對其餘操做符的實現代碼,其實都是同樣的。就顯得有些冗餘了。

而 Swift 正爲咱們解決了這個問題,爲咱們提供了協議的默認實現,仔細查看 Swift 文檔的話,咱們會發現除了 Comparable 協議,還定義了一個 _Comparable 協議,而前者繼承自後者。 _Comparable 協議的定義以下:

protocol _Comparable { ... }
func <(lhs: Self, rhs: Self) -> Bool

咱們發現,剛剛咱們丟失的 < 在這裏找到了。 _Comparable 就是 Swift 中提供的默認協議實現機制的例子。_Comparable 協議中提供了對其餘幾個操做符 >=,>,<= 的默認實現。

因爲 Comparable 同時繼承自 _ComparableEquatable 協議,因此咱們只須要實現 <== 這兩個操做符,便可完成整個比較操做的實現。下面咱們來看一個例子:

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 的概念應該不陌生,那麼若是一個對象想做爲 Dictionarykey 的話,那麼就須要實現 Hashable 協議。

Swift 中如下這些類默認實現了 Hashable 協議:

  • Double
  • Float, Float80
  • Int, Int8, Int16, Int32, Int64
  • UInt, UInt8, UInt16, UInt32, UInt64
  • String
  • UnicodeScalar
  • ObjectIdentifier

因此這些類的實例,能夠做爲 Dictionarykey。 咱們來看一下 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

更多好文,掃碼關注微信公衆號:

相關文章
相關標籤/搜索