Hashable

哈希是一个计算机科学术语,是以一致的方式将一些数据转换为更小的表示形式的过程。哈希值可以通过多种方式生成,但对于所有哈希生成函数,其概念是相同的:

  1. 无论输入大小如何,输出都应具有相同的固定大小
  2. 连续两次计算对象的相同哈希值,应返回相同的值

这两个听起来很简单,但想想看:如果我们得到“Hello World”的哈希值和莎士比亚全集的哈希值,两者最终都会是相同的大小。这意味着不可能将哈希值转换回其原始值——我们无法将 40 个看似随机的十六进制字母和数字转换为莎士比亚的全部作品。虽然没有办法对数据进行【取消哈希】的处理(就像您无法将 40 个字符转换回 10GB 的电影)。但这没关系:最主要的是每条数据的哈希值应该是唯一的,并且也是一致的,以便我们每次都能获得电影的相同哈希值。

通过哈希,可以将 10GB 电影转换为一个短字符串(总共可能有 40 个字符),以唯一地标识它。哈希函数需要一致,这意味着如果我们在本地对电影进行哈希并将其与服务器上的哈希进行比较,它们应该始终相同,并且比较两个 40 个字符的字符串比比较两个 10GB 文件容易得多!所以哈希通常用于数据验证等操作,哈希也与字典键和集合一起使用,这就是他们高效的原因。


遵循 Hashable 协议有以下几个要求:

实现 hash(into:) 方法

遵循 Hashable 协议必须实现一个方法 hash(into:) ,它的作用是计算一个对象的哈希值。具体来说:

<aside> 💡 总之,这个 hash(into:) 方法的作用是为一个遵循 Hashable 协议的类型提供一个确定的哈希值计算方式,从而使该类型可以在基于哈希的集合中高效地存储和查找。

</aside>

struct Person: Hashable {

  let firstName: String
  let lastName: String
  let age: Int
  
  func hash(into hasher: inout Hasher) {
    hasher.combine(firstName)
    hasher.combine(lastName)
    hasher.combine(age)
  }
  
}

实现 hashValue 属性

该协议还要求符合类型具有只读 hashValue 属性,该属性返回表示实例哈希值的整数。

实现 == 运算方法

Hashable 协议继承自 Equatable 协议,因此任何符合 Hashable 的类型也必须提供 == 运算符的实现。

具体参见:重写 == 函数


具体应用

结合 \.self 用作 ForEach ID

ForEach 创建动态视图的方法有一个共同点:即 SwiftUI 需要知道如何唯一地识别每个动态视图,以便可以正确地设置动画。

当使用 Core Data 时,我们可以使用唯一标识符 ID,但也可以使用 \\.self 。当使用 \\.self 作为标识符时,我们指的是“整个对象”,但结构除了自身的属性内容外,是没有任何特定的标识信息的,它的唯一标识符是从哪里来的呢?

实际上其背后的原理是:Swift 会自动计算该结构体实例的哈希值,再将该哈希值作为标识符。并且在计算的过程中,它会把结构实例的所有属性内容,再加上一个叫 objectID 的属性一起计算,因此就算两个实例的属性完全相同,由于 objectID 的加入,它们哈希出来的结果也会不一样。参见:Entity 实体唯一性问题( \.self )

如果想让自定义类型符合 Hashable ,那么只要它内部所有属性内容都符合 Hashable ,就基本不需要做任何工作。

struct Student: Hashable {
    let name: String
}

struct ContentView: View {
    let students = [Student(name: "Harry Potter"), Student(name: "Hermione Granger")]
    var body: some View {
        List(students, id: \\.self) { student in
            Text(student.name)
        }
    }
}

<aside> 💡 当 Xcode 为托管对象生成一个类时,它使该类符合 Hashable ,这是一个协议,意味着 Swift 可以为其生成哈希值,这又意味着我们可以使用 \\.self 为标识符。这也是 String 和 Int 与 \\.self 一起使用的原因:它们也符合 Hashable 。

</aside>


为 NavigationLink 添加附加值

//例如,此结构包含一个 UUID、一个字符串和一个整数:
struct Student {
    var id = UUID()
    var name: String
    var age: Int
}

//如果我们想让该结构符合 Hashable ,我们只需添加如下协议:
struct Student: Hashable {
    var id = UUID()
    var name: String
    var age: Int
}

//现在 Student 结构就符合 Hashable 了,它就可以与 NavigationLink 和 navigationDestination() 一起使用了,就像整数或字符串一样。
//Swift 在内部大量使用 Hashable 。例如,当使用 Set 而不是数组时,放入其中的所有内容都必须符合 Hashable 协议。这就是集合比数组更快的原因。