Swift内建的协议

460 阅读3分钟

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
}

Sequence / Collection

Encodable / Decodable

IteratorProtocol