Swift - Equatable & Comparable
[TOC]
前言
Swift 提供了很对运算符,详情请参考基本运算符
在Swift中,或者其他编程语言中,我们都会通过比较运算符来比较左右两个值。下面我们就来探索一下Swift中这些运算符是怎么实现的。
1. Equatable
1.1 初探
首先我们来看个例子:
var a: Int? = 10
var b:Optional = 20
if a == b {
print("a == b")
}
这里面两个可选类型之间可以通过==
去判断是否相等,我们就去Optional
源码中看看:
extension Optional: Equatable where Wrapped: Equatable {
@inlinable
public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}
}
我们可以看到,在源码中Optional
遵守了Equatable
协议,在实现了==
方法,内部通过模式匹配判断两个值相等或者都为nil
的时候返回true
,否则返回false
。
对于基本类型也可以实现比较:
var a: Int = 10
var b: Int = 20
print(a == b)
false
其实Swift中的类型,可以通过遵守Equatable
协议来使用相等运算符==
和不等运算符!=
来比较两个值相等还是不相等。
Swift标准库中绝大多数的类型都默认实现了Swift.Equatable
协议。比如我们熟知的String
:
extension String : Swift.Equatable {
@inlinable @inline(__always) @_effects(readonly) @_semantics("string.equals") public static func == (lhs: Swift.String, rhs: Swift.String) -> Swift.Bool {
return _stringCompare(lhs._guts, rhs._guts, expecting: .equal)
}
}
对于Int
,在Swift源码中可以看到它是通过标准模块Builtin
中中的比较方法cmp_eq_Int64
来实现的。
@_transparent public static func == (lhs: Swift.Int, rhs: Swift.Int) -> Swift.Bool {
return Bool(Builtin.cmp_eq_Int64(lhs._value, rhs._value))
}
对于其他基本类型的的实现也都大同小异,感兴趣的可以去源码中查看查看,我这里的源码版本是Swift5.3.1
注意:
根据官方文档中的说明:对于元组类型的比较,Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
还有一点需要注意,Bool类型只能比较相等和不等,不能比较大小等
1.2 自定义类型实现==
如果我们对于自定义的类型实现==
,这个时候我们应该怎么做呢?
1.2.1 struct
如果什么都不做,直接写就会报编译错误:
此时给我们的Person
遵守上Equatable
协议就不会报错了。
struct Person: Equatable {
var name: String
var age: Int
}
let p = Person(name: "xiaohei", age: 18)
let p1 = Person(name: "xiaohei", age: 18)
if p == p1 {
print("It's the same person")
}
It's the same person
// 如果将上述实例中的任一属性值修改后则就不会打印上面的结果了
关于上面的打印结果的原因是因为我们遵守了Equatable
协议,系统默认帮我们实现了==
方法,这点可以通过sil
代码进行查看:
我们可以看到这个struct默认有一个==
方法,对应的名称是__derived_struct_equals
,下面我们就找到这个方法看看其内部是如何实现的。
我们可以看到默认的内部实现是分别比较结构体中的值,如果都相等则返回true
,有一个不同则返回false
当然我们也可以自定义实现,我们认为两个人名字相同即是同一个人:
struct Person: Equatable {
var name: String
var age: Int
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.name == rhs.name
}
}
let p = Person(name: "xiaohei", age: 18)
let p1 = Person(name: "xiaohei", age: 19)
if p == p1 {
print("It's the same person")
}
It's the same person
1.2.2 class
在class
中如果不遵守Equatable
协议会和struct
一样报编译错误:
当我们遵守Equatable
协议后,会提示我们去实现协议中的==
方法
实现后如下:
class Person: Equatable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
}
let p = Person(name: "xiaohei", age: 18)
let p1 = Person(name: "xiaohei", age: 18)
if p == p1 {
print("It's the same person")
}
It's the same person
当然,我们自己实现==
方法就可以按照我们的需求去返回里面的结果,这里判断了name
个age
,在开发中我们可能更注重判断uuid
,但是这里没有。这里就给了开发者选择的空间。
这里还有多说一点,如果我们的属性都是可选类型的:
class Person: Equatable {
var name: String?
var age: Int?
init(name: String, age: Int) {
self.name = name
self.age = age
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.age == rhs.age
}
}
let p = Person(name: "xiaohei", age: 18)
let p1 = Person(name: "xiaohei", age: 19)
if p == p1 {
print("It's the same person")
}
生成sil
代码:
我们可以通过sil
代码看见,对于可选类型,其比较是通过可选类型的==
进行比较的。
1.3 ===
Swift 也提供恒等(===
)和不恒等(!==
)这两个比较符来判断两个对象是否引用同一个对象实例。
所以我们要区分==
和===
-
==
相当于equal to,用于判断两个值是否相等 -
==
用于判断两个对象是否引用同一个对象实例 -
!=
和!==
也与上述一致
1.4 源码
1.4.1 ==和!=
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
extension Equatable {
@_transparent
public static func != (lhs: Self, rhs: Self) -> Bool {
return !(lhs == rhs)
}
}
我们可以看到Equatable
是一个协议,那些需要遵守Equatable
协议的可以自己实现内部的内容。如果实现了==
后面给了默认实现!=
,就是对==
的结果取反,这点设计很精妙。
1.4.2 === 和 !==
关于===
和!==
的源码实现这里是两个全局函数。
===
内部是比较左右两的对象的唯一标识,即通过ObjectIdentifier
或取到的唯一标识,如果相同或者都为空则返回true
,否则返回false
。
!==
是对===
的结果取反。
@inlinable // trivial-implementation
public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return ObjectIdentifier(l) == ObjectIdentifier(r)
case (nil, nil):
return true
default:
return false
}
}
@inlinable // trivial-implementation
public func !== (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
return !(lhs === rhs)
}
2. Comparable
除了Equatable
还有Comparable
,其中定义了< 、<=、>=、>
四个比较运算符,以及扩展了...、..<
区间运算符。
2.1 源码
这次我们先看看源码:
以下代码在Comparable.swift
文件中,看了其扩展实现,只能用一个妙字来形容,此处我们只需自己实现一个<
比较运算符即可实现全部比较运算:
public protocol Comparable: Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
extension Comparable {
@inlinable
public static func > (lhs: Self, rhs: Self) -> Bool {
return rhs < lhs
}
@inlinable
public static func <= (lhs: Self, rhs: Self) -> Bool {
return !(rhs < lhs)
}
@inlinable
public static func >= (lhs: Self, rhs: Self) -> Bool {
return !(lhs < rhs)
}
}
并在ClosedRange.swift
文件中实现了如下代码,此处是闭区间运算符:
extension Comparable {
@_transparent
public static func ... (minimum: Self, maximum: Self) -> ClosedRange {
_precondition(
minimum <= maximum, "Can't form Range with upperBound < lowerBound")
return ClosedRange(uncheckedBounds: (lower: minimum, upper: maximum))
}
}
在Range.swift
文件中实现了如下代码,此处是半开区间运算符:
@_transparent
public static func ..< (minimum: Self, maximum: Self) -> Range {
_precondition(minimum <= maximum,
"Can't form Range with upperBound < lowerBound")
return Range(uncheckedBounds: (lower: minimum, upper: maximum))
}
2.2 使用示例
此处我们以struct
为例,重写<
比较运算符
struct Person: Comparable {
var name: String
var age: Int
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.age < rhs.age
}
}
let p = Person(name: "xiaohei", age: 18)
let p1 = Person(name: "xiaohei", age: 19)
if p < p1 {
print("p < p1")
} else {
print("p >= p1")
}
p < p1
关于<
在基本类型中的实现也可以在源码中找到:
@_transparent public static func < (lhs: Swift.Int, rhs: Swift.Int) -> Swift.Bool {
return Bool(Builtin.cmp_slt_Int64(lhs._value, rhs._value))
}
extension String : Swift.Comparable {
@inlinable @inline(__always) @_effects(readonly) public static func < (lhs: Swift.String, rhs: Swift.String) -> Swift.Bool {
return _stringCompare(lhs._guts, rhs._guts, expecting: .less)
}
}
3. 总结
本文介绍了Swift提供给开发者的两个协议Equatable & Comparable
-
Equatable
协议中有==
这个方法 - 通过扩展实现了中对
==
取反实现了!=
- 在其实现文件中还有两个全局方法分别实现了
===
和!==
-
===
比较的是两个AnyObject
的通过ObjectIdentifier
获取的唯一标识 -
!==
是对===
结果的取反
-
-
Comparable
协议中有< 、<=、>=、>
方法 - 并在扩展中首先实现了
<=、>=、>
方法,所以开发者只需自己实现<
方法就相当于同时实现了以上4个方法 -
Comparable
协议有通过另外的扩展实现了...、..<
区间运算符,具体实现详见2.1中的源码