Swift 协议之 Equatable

18 阅读3分钟

在 Swift 中,Equatable 是一个非常常见的协议。它的作用是判断两个值是否相等,是 Swift 中比较两个值最直接、最常见的方式。

如果我们需要判断两个字符串是否相等,通常会用下面的方式来实现:

let str1 = "Swift"
let str2 = "Swift"

let result = (str1 == str2)

那为什么字符串类型可以直接使用 == 操作符呢?答案就是因为系统已经给字符串类型实现了 Equatable 协议。同样的还有 IntArray 等系统类型都默认实现了该协议。

下面,我们先来看一下 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协议的话,这一行会编译报错。