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


 

在我們進行 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

上面的代碼中,我們定義了兩個數組,arr1arr2 這兩個數組中保存的是同樣的元素。接下來我們對他們進行相等比較並輸出結果。第一次我們用 == 等於號進行比較,返回的結果是 0, 也就是說比較失敗了。原因相信有些經驗的同學都應該明白,是因為 在 Objective-C 中 == 比較的是引用,因為 arr1arr2 是兩個不同的對象,所以即便他們的值相同,但他們的引用也不相同。所以 == 比較會失敗。而第二個比較用得是 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 類,並實例化了這個類的兩個對象 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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM