[SwiftUI 100 天] Cupcake Corner - part5 ObservableObject

659 阅读4分钟
译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀


编码一个 ObservableObject 类

我们已经重新组织了代码,以便一个Order对象能在多屏之间共享,其好处是我们可以不同屏之间来回移动而不丢失数据。不过,这种方法的代价是,我们需要对类的属性使用@Published属性包装器,并且因为做我们失去了自动的Codable协议支持。

如果你不相信,可以修改Order的定义,然后添加Codable,像这样:

class Order: ObservableObject, Codable {

编译将失败,因为 Swift 不知道如何编码和解码 published 属性。这会是一个问题,因为我们希望提交用户的订单到某个网络服务器,意味着我们需要用到 JSON —— 我们需要Codable协议能够工作。

解决方案是手工添加Codable实现,也就是说,告诉 Swift 哪些东西需要被编码,如何编码,以及如何解码 —— 即从 JSON 转回 Swift 数据。

第一步是添加一个遵循CodingKey的枚举,列出所有我们想要保存的属性。在我们的Order类中,几乎就是所有属性 —— 唯一不需要编解码的是静态的types属性:

把这个枚举添加到Order

enum CodingKeys: CodingKey {
    case type, quantity, extraFrosting, addSprinkles, name, streetAddress, city, zip
}

第二步要求我们实现encode(to:)方法,创建一个使用我们刚刚创建的 coding keys 枚举的容器,然后把所有属性附着到对应的键。基本上,这是重复调用encode(_:forKey:)的过程,每次传入不同的属性和 coding key。

把这个方法添加到Order

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)

    try container.encode(type, forKey: .type)
    try container.encode(quantity, forKey: .quantity)

    try container.encode(extraFrosting, forKey: .extraFrosting)
    try container.encode(addSprinkles, forKey: .addSprinkles)

    try container.encode(name, forKey: .name)
    try container.encode(streetAddress, forKey: .streetAddress)
    try container.encode(city, forKey: .city)
    try container.encode(zip, forKey: .zip)
}

因为这个方法被标记了throws,所以我们需要担心内部可能抛出的任何错误 —— 我们可以使用try但不用catch,因为我们知道问题会前向传播,并在其他的某个地方被处理。

我们的最后步骤是实现一个必要的构造器,用以从某份归档数据中解码出一个Order实例。这基本上就是编码的反过程,同样可以得益于throws的好处。

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    type = try container.decode(Int.self, forKey: .type)
    quantity = try container.decode(Int.self, forKey: .quantity)

    extraFrosting = try container.decode(Bool.self, forKey: .extraFrosting)
    addSprinkles = try container.decode(Bool.self, forKey: .addSprinkles)

    name = try container.decode(String.self, forKey: .name)
    streetAddress = try container.decode(String.self, forKey: .streetAddress)
    city = try container.decode(String.self, forKey: .city)
    zip = try container.decode(String.self, forKey: .zip)
}

值得一提的是,你可以以任意顺序编码你的数据 —— 不需要和对象中声明属性的顺序保持一致。

这样一来,我们就完成满足了Codable协议:我们可以有效地绕过@Published属性包装器,直接读写属性的值。但是,我们的代码仍然编译不错 —— 实际上,现在我们面对的是一个完全不同的错误,它位于 ContentView.swift.

现在的问题是,我们刚刚为Order类创建了一个自定义构造器,init(from:),并且 Swift 希望我们在各个地方使用它 —— 即便我们只想要创建一个空的订单。

所幸的是 Swift 允许我们给一个类添加多个构造器,以便我们可以使用任何数量的不同方式创建对象。在这个案例下,我们需要写一个新的构造器,创建一个不包含任何数据的订单 —— 这些属性值将完全依赖我们给属性赋的默认值。

把下面这个新的构造器添加到Order

init() { }

现在我们的代码能够编译通过了,因为Codable协议已被完全遵循。接下来我们进入最后一步:通过网络发送和接收Order对象。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~