我只是偶然发现了一些东西,但我在任何地方都找不到记录的行为,所以我想检查它是否正确。
场景:
我制作了一个 Swift 应用程序,可以下载并解析 JSON 文件。请注意,实际实现更复杂,因此解码需要自定义实现(以下示例中不是这种情况,但它表现出与我想展示的行为相同的行为)
对象如下所示:
enum ScreenName: String, Decodable, CaseIterable {
case screen1 = "screen1"
case screen2 = "screen2"
}
struct ScreenContents: Decodable {
...
}
struct Screens {
let screens: [ScreenName: ScreenContents]
enum CodingKeys: String, CodingKey {
case screens
}
}
数据:
{
"screens":{
"screen1":{..},
"screen2":{..}
}
}
加载Screens
的实现:
extension Screens: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let screensContainer = try container.nestedContainer(keyedBy: ScreenName.self, forKey: .screens)
for enumKey in screensContainer.allKeys {
....
}
}
这工作正常。从 URL 下载的数据被解析为一切正常。
现在我想发布我的应用程序的新版本,我在其中介绍了case screen3 = "screen3"
。对于新版本的应用程序,没有isse,该项目被添加到枚举中,服务器上的JSON被更新,一切正常。
但是我担心向后兼容性。由于生产环境中应用程序的当前版本不知道screen3
,因此 Swift 无法将传入的字符串转换为枚举,会发生什么?是否会在init(from: decoder)
中抛出异常?
所以我尝试使用上面的代码加载一个包含screen3
的最新文件。令我惊讶的是,它没有抛出任何错误。
实际发生的是
let screensContainer = try container.nestedContainer(keyedBy: ScreenName.self, forKey: .screens)
忽略screen3
条目,仅返回screen1
和screen2
。
这显然是个好消息,因为我的向后兼容性问题已经解决,但是我希望看到它得到某种文档的支持,所以我在使用它之前不依赖错误或其他怪癖,我没有找到任何。
谁能帮忙?
谢谢
CodingKey的全部意义在于它列出了您感兴趣的键。键控容器是对数据的视图,可能不包括所有数据(在您的情况下,它不包括screen3
)。allKeys
的文档涉及这一点(着重号是加的):
来自同一解码器的不同密钥容器可能会在此处返回不同的密钥,因为可以使用无法相互转换的多个密钥类型进行编码。这应报告存在的所有可转换为请求类型的密钥。
您请求的类型是屏幕名称,它是一个枚举,支持两个特定的字符串。所以这些是你唯一能得到的东西。但是编码密钥不一定是枚举。例如,我可以构建一个结构体 CodingKey(我一直这样做,并希望它能被添加到 stdlib 中)。
struct StringKey: CodingKey, Hashable, CustomStringConvertible {
var description: String {
return stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
这是一个可以接受任何字符串的编码密钥(我用它来解码任意 JSON,其中密钥可能事先不知道)。我可以构建另一个只接受以某个前缀开头的字符串并为其他所有内容返回 nil 的字符串。编码密钥可以做各种各样的事情。
但最终规则是,键控容器仅包含可转换为请求的 CodingKey 的键条目。
所以是的,这是正式向后兼容的。你不只是幸运。这就是它的工作原理。