Swift 协议详解:Equatable、Hashable、Identifiable、Comparable

Swift 是一个强类型的语言,它通过协议(Protocol)为类型提供功能扩展的强大能力。本篇文章将深入解析四个在开发中非常常见也非常重要的协议:EquatableHashableIdentifiableComparable。掌握它们不仅可以写出更简洁、更强大的代码,也能够更好地与 Swift 标准库(如数组、集合、字典等)进行交互。


Equatable 协议

什么是 Equatable

Equatable 是 Swift 中用于判断**两个值是否"相等"**的协议。只要一个类型遵循了 Equatable,你就可以使用 ==!= 运算符来比较两个该类型的实例是否相等。

Swift 中大多数内建类型(如 IntStringDoubleArrayDictionary 等)都已经默认实现了 Equatable


语法定义

swift 复制代码
protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

这表示:你需要实现一个静态函数 ==,接收两个同类型的参数,返回它们是否相等的布尔值。


使用场景

  • 比较两个结构体或枚举是否"内容一致"
  • 在集合操作中使用,如 contains()firstIndex(of:)
  • 用于控制流中,如 if x == y { ... }
  • 搭配 Hashable 自动派生哈希值逻辑

自动合成 ==

当你的结构体中所有属性都遵循 Equatable 时,Swift 会自动为你合成 == 运算符的实现,不需要手动写。

swift 复制代码
struct Person: Equatable {
    let name: String
    let age: Int
}

// 编译器合成逻辑相当于:
/*
static func == (lhs: Person, rhs: Person) -> Bool {
    return lhs.name == rhs.name && lhs.age == rhs.age
}
*/

let p1 = Person(name: "Alice", age: 25)
let p2 = Person(name: "Alice", age: 25)
let p3 = Person(name: "Bob", age: 30)

print(p1 == p2)  // true
print(p1 == p3)  // false

手动实现 ==(部分属性比较)

有时候你不想比较所有属性,只关注关键字段,这种情况下可以自定义 == 实现

swift 复制代码
struct Book: Equatable {
    let title: String
    let author: String
    let isbn: String

    // 只比较 ISBN,忽略作者和书名
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.isbn == rhs.isbn
    }
}

这对某些实际场景非常有用,比如比较唯一标识符是否一致、或者忽略元数据差异。


注意事项

规则 是否自动合成
所有属性都遵守 Equatable ✅ 是
有任意一个属性不符合 Equatable ❌ 否
已自定义 == 实现 ❌ 编译器不会再自动合成
引用类型 class 默认不合成 ==(需手动) ❌ 否

与引用类型区别

对于 class(引用类型):

  • == 不会自动合成,即使所有属性都符合 Equatable
  • 默认比较的是内存地址是否相同 (使用 ===);
  • 如果你希望比较内容一致性,必须手动实现 Equatable

Hashable 协议

什么是 Hashable?

Hashable 协议允许你的类型用于集合类型如 Set,或作为 Dictionary 的键(key)。它要求类型能够生成一个哈希值,供底层哈希表结构判断元素存储位置与是否相等。

当一个类型遵循 Hashable,它也必须遵循 Equatable,因为哈希值相同的两个对象,还需通过 == 判断是否真的"相等"。

语法定义

swift 复制代码
protocol Hashable: Equatable {
    func hash(into hasher: inout Hasher)
}

使用场景

• Set 中的元素类型 Element 必须是 Hashable

• Dictionary<Key, Value> 中的 Key 类型必须是 Hashable

• 快速查找/去重(基于哈希表)

自动合成 Hashable

当一个结构体中所有属性本身都是 Hashable 类型时,Swift 会自动合成 hash(into:) 和 ==。

swift 复制代码
struct User: Hashable {
    let id: Int
    let username: String
}

var users: Set<User> = []
users.insert(User(id: 1, username: "Tom"))
users.insert(User(id: 2, username: "Jerry"))

// User(id: 1, username: "Tom") == User(id: 1, username: "Tom") → true

此时,Swift 自动合成的等价代码:

swift 复制代码
extension User {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(username)
    }

    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id && lhs.username == rhs.username
    }
}

自定义哈希逻辑

有时你只希望根据关键字段判断相等性,例如只按 id 判断唯一性:

swift 复制代码
struct Product: Hashable {
    let id: Int
    let name: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: Product, rhs: Product) -> Bool {
        return lhs.id == rhs.id
    }
}

此时 Set 中即使 name 不同,只要 id 相同就认为是重复元素。

注意事项

  • Hashable 自动合成仅适用于 所有属性都是 Hashable 的结构体或枚举;
  • 如果你手动实现了 hash(into:),就要确保 == 与哈希值的一致性(即"相等的对象必须有相同的哈希值");
  • 引用类型 class 也可以遵循 Hashable,但不会自动合成,需要你手动实现;
  • 如果类型不符合 Hashable,就不能作为 Set 元素或 Dictionary 的键,编译报错。
容器类型 元素/键的类型约束
Set Element: Hashable
Dictionary<Key, Value> Key: Hashable

Identifiable 协议

什么是 Identifiable?

Identifiable 协议定义了一个稳定的唯一标识符。这在 SwiftUI 中尤其有用,用于标识列表(ListForEach)中的元素。

语法定义

swift 复制代码
protocol Identifiable {
    associatedtype ID: Hashable
    var id: ID { get }
}

使用场景

  • SwiftUI 的 ListForEach 自动识别元素
  • 标识数据库或网络对象
  • 建模唯一对象实体

示例

swift 复制代码
struct Task: Identifiable {
    let id: UUID = UUID()
    let title: String
}

在 SwiftUI 中的应用:

swift 复制代码
struct TaskListView: View {
    let tasks = [
        Task(title: "Write Blog"),
        Task(title: "Read Book")
    ]

    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

Comparable 协议

什么是 Comparable?

遵循 Comparable 协议的类型可以进行大小比较,比如 <<=>>=。它默认要求你实现 < 运算符。

语法定义

swift 复制代码
protocol Comparable: Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool
}

使用场景

  • 数组排序 sorted()sort()
  • 最值计算:minmax
  • 排序操作中用作键值

示例

swift 复制代码
struct Score: Comparable {
    let value: Int

    static func < (lhs: Score, rhs: Score) -> Bool {
        return lhs.value < rhs.value
    }
}

let scores = [Score(value: 10), Score(value: 30), Score(value: 20)]
let sorted = scores.sorted()

你也可以让多个属性参与比较:

swift 复制代码
struct Player: Comparable {
    let name: String
    let score: Int

    static func < (lhs: Player, rhs: Player) -> Bool {
        if lhs.score == rhs.score {
            return lhs.name < rhs.name
        }
        return lhs.score < rhs.score
    }
}

总结

协议 作用 常见应用
Equatable 判断两个值是否相等 if 判断、查找、比较等
Hashable 提供哈希值用于集合、字典等 Set、Dictionary 的键、去重
Identifiable 提供唯一标识符 SwiftUI List、模型标识
Comparable 值之间的大小关系比较 排序、筛选、最值计算等

通过为你的自定义类型实现这些协议,不仅可以获得 Swift 提供的强大集合操作能力,还能让代码更具表达力与扩展性。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。