哈希是一个计算机科学术语,是以一致的方式将一些数据转换为更小的表示形式的过程。哈希值可以通过多种方式生成,但对于所有哈希生成函数,其概念是相同的:
这两个听起来很简单,但想想看:如果我们得到“Hello World”的哈希值和莎士比亚全集的哈希值,两者最终都会是相同的大小。这意味着不可能将哈希值转换回其原始值——我们无法将 40 个看似随机的十六进制字母和数字转换为莎士比亚的全部作品。虽然没有办法对数据进行【取消哈希】的处理(就像您无法将 40 个字符转换回 10GB 的电影)。但这没关系:最主要的是每条数据的哈希值应该是唯一的,并且也是一致的,以便我们每次都能获得电影的相同哈希值。
通过哈希,可以将 10GB 电影转换为一个短字符串(总共可能有 40 个字符),以唯一地标识它。哈希函数需要一致,这意味着如果我们在本地对电影进行哈希并将其与服务器上的哈希进行比较,它们应该始终相同,并且比较两个 40 个字符的字符串比比较两个 10GB 文件容易得多!所以哈希通常用于数据验证等操作,哈希也与字典键和集合一起使用,这就是他们高效的原因。
遵循 Hashable
协议有以下几个要求:
遵循 Hashable
协议必须实现一个方法 hash(into:)
,它的作用是计算一个对象的哈希值。具体来说:
hash(into:)
方法接受一个 inout Hasher
类型的参数,Hasher
是一个负责计算哈希值的类型hasher.combine()
将对象的一个或多个属性添加到 hasher
中,它们会用于生成最终的哈希值Set
或 Dictionary
)时非常重要,因为集合类型依赖于对象的哈希值来快速查找和比较对象<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
属性,该属性返回表示实例哈希值的整数。
Hashable
协议继承自 Equatable
协议,因此任何符合 Hashable
的类型也必须提供 ==
运算符的实现。
具体参见:重写 == 函数
ForEach
创建动态视图的方法有一个共同点:即 SwiftUI 需要知道如何唯一地识别每个动态视图,以便可以正确地设置动画。
如果对象符合 Identifiable
协议,那么 SwiftUI 将自动使用其 id
属性进行唯一化
如果对象不符合 Identifiable
协议,可以通过 id: \\.name
来指定具有唯一性特点的属性(例如书籍 ISBN 号)作为 ID
但如果对象不符合 Identifiable
协议,并且也没有具有唯一性特点的属性,那通常可以使用 ForEach(_:,id:\\.self)
,但这里有个要求就是该对象需要遵循 Hashable
协议
关于 ForEach
的 ID 参数设置的几种情况,详情可以看这里 参数:ID
// 遵循 Hashable 协议
****extension CalculatorButtonItem: Hashable {}
// 我们往往直接将 \\.self 用于一些基本类型(Int 和 String等),因为 Swift 大多数内置类型已经符合 Hashable 协议
ForEach([2, 4, 6, 8, 10], id: \\.self) {
Text("\\($0) is even")
}
当使用 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>
Hashable
的协议Hashable
协议,例如 Int
String
Date
URL
数组、字典。如果创建了一个具有全部符合 Hashable
属性的自定义结构,则只需做一点更改,即可让该结构遵循 Hashable
协议//例如,此结构包含一个 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 协议。这就是集合比数组更快的原因。