译自 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及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~