Swift Explore - 关于 Swift 中的 isEqual 的一点探索
2015-12-22 20:54
441 查看
在我们进行 App 开发的时候,经常会用到的一个操作就是判断两个对象是否相等。比如两个字符串是否相等。而所谓的 相等 有着两层含义。一个是值相等,还有一个是引用相等。如果熟悉 Objective-C 开发的话,就会知道 Objective-C 为我们提供了一系列
上面的代码中,我们定义了两个数组,arr1 和 arr2 这两个数组中保存的是同样的元素。接下来我们对他们进行相等比较并输出结果。第一次我们用 == 等于号进行比较,返回的结果是 0, 也就是说比较失败了。原因相信有些经验的同学都应该明白,是因为 在 Objective-C 中 == 比较的是引用,因为 arr1 和 arr2 是两个不同的对象,所以即便他们的值相同,但他们的引用也不相同。所以 == 比较会失败。而第二个比较用得是 NSArray 的 isEqualToArray 方法,这个方法是用来进行值比较的,因为这两个数组的值相同,所以 isEqualToArray 的比较方法会返回 1。
<!-- more -->
![](http://www.theswiftworld.com/images/swift_explore_equal_1.jpg)
Swift 中没有提供 iEqual 方法的定义,而是用另一种更加自然的方式,重载操作符 来处理相等判断的。重载
Swift 中自己实现了数组类,叫做
我们看到,在 Swift 中直接使用
![](http://www.theswiftworld.com/images/swift_explore_equal_2.jpg)
上面的代码中,我们定义了一个
那么下面我们对这个类进行一下修改:
在我们的修改中,我们实现了
进行完这个修改后,我们就可以继续使用我们之前的判断了:
这次,
现在我们知道了 Swift 中的大部分类都有自己对
我们可以使用
我们用
实现了
你是不是会觉得,要实现比较操作,我们要实现这里面所有的方法呢。我们不妨花几分钟时间仔细的看一看这个协议的定义,并且思考一下。
首先,顺序比较的操作符有四个,
然后,我们再分析一下这些操作符的逻辑关系,
通过上面简短的分析,我们是不是发现了一个规律? 其实就是,我们只要实现某几个操作符,就能推出其他操作符的逻辑。举个例子,以上面的
那么,我们
仔细想一想,其实我们只要实现
>= 可以通过
> 可以通过
<= 可以通过
关于这方面的知识,有一个概念叫做 严格全序,有兴趣的同学可以读一读。
那么现在问题又来了,我们其实只要实现
而 Swift 正为我们解决了这个问题,为我们提供了协议的默认实现,仔细查看 Swift 文档的话,我们会发现除了
我们发现,刚刚我们丢失的
由于
我们在这里完整的实现了
Swift 中以下这些类默认实现了 Hashable 协议:
Double
Float, Float80
Int, Int8, Int16, Int32, Int64
UInt, UInt8, UInt16, UInt32, UInt64
String
UnicodeScalar
ObjectIdentifier
所以这些类的实例,可以作为 Dictionary 的 key。 我们来看一下 Hashable 协议的定义:
定义非常简单,我们只需要实现
接下来我们就将它作为字典的 key 了:
这片文章介绍了 Swift 中的三个协议
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 的定义。![](http://www.theswiftworld.com/images/swift_explore_equal_1.jpg)
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协议。那如果对没实现这个协议的对象使用
==进行比较呢?我们可以看一下这段代码:
![](http://www.theswiftworld.com/images/swift_explore_equal_2.jpg)
上面的代码中,我们定义了一个
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语句中的
现在我们知道了 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 中的大部分类,基本都实现了这几个协议。可以说虽然我们经常遗忘他们,但它们又无时无刻不在我们左右。了解他们之后,一定会对我们大有帮助。
相关文章推荐
- iOS -- dismissViewControllerAnimated(Swift)
- swift控件之旅之UITextField
- swift 8583报文组装库
- 【XCode】Xcode7.2(7C68)下Swift基于SpriteKit出现Invalid Request: requesting subtype without specifying idiom
- Swift绘图
- swift 获取类的所有属性、获取对象的属性值、设置对象的属性值
- swift纯代码实现UITableview总结一
- 项目开发笔记-2015.12.22-swift
- Swift学习笔记(2):willSet与didSet
- swift 学习记录(继承)
- swift --2
- Swift如何实现代理,block传值
- swift字符串相关用法速查表
- swift 属性
- Swift中的willSet与didSet
- Swift学习笔记(1):断言(Assertions)
- Swift的初始化方法
- swift学习日志——可变参数
- Swift中元组(Tuples),结构体(Struct),枚举(Enums)之间的区别
- AFNetwork swift版Alamofire使用问题集锦