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

更多好文,扫码关注微信公众号:

相关文章
相关标签/搜索