在 Swift 中,Equatable
是一个非常常见的协议。它的作用是判断两个值是否相等,是 Swift 中比较两个值最直接、最常见的方式。
如果我们需要判断两个字符串是否相等,通常会用下面的方式来实现:
let str1 = "Swift"
let str2 = "Swift"
let result = (str1 == str2)
那为什么字符串类型可以直接使用 ==
操作符呢?答案就是因为系统已经给字符串类型实现了 Equatable
协议。同样的还有 Int
、Array
等系统类型都默认实现了该协议。
下面,我们先来看一下 Equatable 都包含什么内容。
Equatable 接口
Equatable
是一个标准库协议,它定义了一个基本的接口:
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
从上面的源码可以看到,这个协议只要求实现一个方法:==
。
接下来,我们看一下什么场景下需要使用到这个协议。
Equatable 的使用场景
判断两个值是否相等,在日常开发中是非常常见的操作,比如以下的场景
- 比较两个结构体是否代表同一个实体;
- 判断数组中是否包含某个元素;
- 在集合(如
Set
)中去重; - 在 UI 中判断状态是否变化,是否需要刷新;
虽然,在 Swift 的标准库中,很多系统类型都遵守了 Equatable
。但我们开发中不可能只使用标准库提供的类型,很多情况下,我们需要自定义类型。那么,如何让自定义的类型也能使用 ==
操作符呢?
class Person {
let name: String
let id: Int
init(name: String, id: Int) {
self.name = name
self.id = id
}
}
let jack = Person(name: "jack", id: 123)
let rose = Person(name: "rose", id: 234)
print(jack == rose) // 编译报错 Binary operator '==' cannot be applied to two 'Person' operands
比如,上面我们自定义了一个 Person
的类,并且构建了两个实例对象,如果直接对两个对象使用 ==
操作符,会导致上面的编译报错。
如何让自定义类型遵守 Equatable
对与我们自定义的类型,有两种方式可以遵守 Equatable。
方式一:自动合成
如果我们定义的结构体或枚举的所有成员都已经是遵守 Equatable
的类型,Swift 会自动帮你合成 ==
实现。只要显式声明 Equatable
,就能直接使用。比如我们上面举例的代码,我们只需要改动两个地方就可以让其遵守 Equatable
协议:
- 将
class
改为struct
; - 在 Person 后面显式的写出
Equatable
协议;
代码如下:
struct Person: Equatable {
let name: String
let id: Int
init(name: String, id: Int) {
self.name = name
self.id = id
}
}
let jack = Person(name: "jack", id: 123)
let rose = Person(name: "rose", id: 234)
print(jack == rose)
需要注意的是:自动合成 ==
只在以下条件下成立:
- 所有属性都遵守
Equatable
; - 没有提供自定义的
==
实现; - 类型是结构体或者枚举;
如果我们想用类的话,或者想自定义比较逻辑的话。只能只用第二种方式:手动实现 Equatable
协议的方法。
方式二:手动实现
手动实现的示例代码如下:
extension Person: Equatable {
static func == (lhs: Person, rhs: Person) -> Bool {
lhs.id == rhs.id
}
}
其余代码保持不变,我们只需用给 Person 添加一个扩展,并在扩展中实现 ==
函数即可。这种方式更加灵活,因为我们可以在函数体里面自定义我们的比较逻辑。
Equatable 与泛型
当我们声明泛型函数的时候,可以给参数添加 Equatable
限制,以便进行参数比较,这样也可以更好的提高代码的健壮性。示例代码如下:
func areEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
print(areEqual(3, 3)) // true
print(areEqual("hi", "hello")) // false
let jack = Person(name: "jack", id: 123)
let rose = Person(name: "rose", id: 234)
print(areEqual(jack, rose)) // 如果 Person 没有遵守 Equatable协议的话,这一行会编译报错。