Equatable
用途:支持 == 和 != 操作。结构体大多可自动合成。
Hashable
Hashable 继承自 Equatable,所以你必须实现 ==(除非自动合成)。这是因为哈希集合必须先判断两个对象是否“可能相等”(哈希值相同),再进一步通过 == 确定真正相等。
Hashable 协议表示一个类型可以被“哈希”(即计算出一个唯一的整数值),从而可以用作:
- Set 的元素
- Dictionary 的 key
- 快速比较和去重的数据结构
Swift 标准库中如 String、Int、Bool、Double、UUID 等都已经是 Hashable 的。
实现 Hashable 协议的类型必须:
- 实现 hash(into:) 方法 —— 用于生成哈希值
- 实现 Equatable(因为相等的对象必须有相同的哈希值)
从 Swift 4.1 开始,大多数情况下可以自动合成这些功能(自动实现 == 和 hash(into:))。Swift 结构体只要其成员是 Hashable,就能自动合成 Hashable。
手动合成
如果只希望用 部分字段 来判断哈希和相等性,可以自定义hash(into:),必须也提供 ==。这在你希望“唯一性”只依赖于某个字段时非常有用。
struct Article: Hashable {
let id: Int
let title: String
let content: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Article, rhs: Article) -> Bool {
return lhs.id == rhs.id
}
}
哈希值如何工作?
每个 Hashable 类型都可以通过 hash(into:) 方法将自己的关键属性组合进系统的 Hasher 类型中:
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(name)
}
Swift 使用的是一种 随机种子 的哈希方式,每次运行哈希值可能不同,但在同一次运行中,哈希值是一致的。
Comparable
用途:支持大小比较、排序。
struct Score: Comparable {
let value: Int
static func < (lhs: Score, rhs: Score) -> Bool {
return lhs.value < rhs.value
}
}
let scores = [Score(value: 80), Score(value: 100), Score(value: 60)]
let sorted = scores.sorted() // 默认从小到大排序
Codable(= Encodable + Decodable)
用途:主要用于数据的序列化和反序列化,比如 JSON 和 Plist 的编码(Encoding)和解码(Decoding)。
- Encodable:可以将数据编码成外部格式(如 JSON)
- Decodable:可以将外部数据 解码成 Swift 对象
Swift 会在绝大多数情况下自动合成这两个协议的实现。
struct Book: Codable {
let title: String
let pages: Int
}
解码
let json = """
{
"title": "Swift Guide",
"pages": 300
}
""".data(using: .utf8)!
let book = try JSONDecoder().decode(Book.self, from: json)
print(book.title)
编码
let newUser = User(id: 202, name: "Bob", email: "bob@example.com")
let encodedData = try JSONEncoder().encode(newUser)
let jsonString = String(data: encodedData, encoding: .utf8)!
print(jsonString)
// {"id":202,"name":"Bob","email":"bob@example.com"}
可选字段、默认值处理
struct User: Codable {
let id: Int
let nickname: String? // 可选字段;缺失字段 不会引发解码错误。
}
Swift 5.5+,支持提供默认值,如果 JSON 中没有 nickname,就会用默认值。
自定义字段名映射(CodingKeys)
如果 JSON 字段名与模型属性名不一致,Swift 会使用你提供的CodingKeys来做字段映射。
struct User: Codable {
let id: Int
let name: String
enum CodingKeys: String, CodingKey {
case id = "user_id"
case name = "user_name"
}
}
JSONEncoder/Decoder支持 自定义配置。如:字段自动转驼峰式命名
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // 驼峰
手动实现 init(from:) 和 encode(to:)
适用于更复杂或自定义行为的情况
struct User: Codable {
let id: Int
let name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
}
}
CustomStringConvertible
用途:控制打印输出。
struct Car: CustomStringConvertible {
var brand: String
var description: String {
return "Car brand is \(brand)"
}
}
let car = Car(brand: "Tesla")
print(car) // Car brand is Tesla
优点:自动支持嵌套模型结构和数组,不需要额外处理。(只需对应好)
Identifiable
用途:用于 SwiftUI 和 DiffableDataSource 识别唯一实体。
Error
用途:用于自定义错误类型,和 throw 配合使用。
enum NetworkError: Error {
case notConnected
case timeout
}
func fetch() throws {
throw NetworkError.timeout
}