Swift 内置的Codable API的主要优势之一是编译器能够在使用它时自动合成许多不同的编码和解码实现。在很多情况下,我们只需将Swift类型标记为Codable ,就可以将其序列化为JSON等格式,剩下的就由编译器处理了。
让我们来看看这种自动合成是如何具体用于枚举的,以及系统的这一部分是如何在 Swift 5.5 中得到升级的。
原始可表示的枚举
枚举通常有两种变体--一种是由原始值支持的枚举(如Int 或String ),另一种是包含关联值。自从Swift 4.0中引入Codable以来,属于前一类的枚举一直支持编译器合成。
因此,举例来说,假设我们正在开发一个应用程序,其中包括以下String-支持的枚举,它符合Codable 。
enum MediaType: String, Codable {
case article
case podcast
case video
}
由于编译器能够自动合成所有需要用原始值对枚举进行编码和解码的代码,我们通常不需要自己编写更多的代码--这意味着我们现在可以在其他Codable 类型中自由使用上述枚举,比如这样:
struct Item: Codable {
var title: String
var url: URL
var mediaType: MediaType
}
如果我们现在将上述Item 类型的一个实例编码为JSON,那么我们会得到下面这种输出(因为MediaType 的值会自动使用支持它们的原始String 值进行编码和解码):
{
"title": "Swift by Sundell",
"url": "https://swiftbysundell.com/podcast",
"mediaType": "podcast"
}
到目前为止还不错。但是,如果我们反而想对支持关联值的枚举进行编码或解码呢?让我们接下来看看这个问题。
关联值
在 Swift 5.5 之前,如果我们想让一个包含关联值的枚举符合Codable ,那么我们就必须手动编写所有的代码。然而,现在情况不同了,因为编译器已经得到了升级,现在它也能够为这类枚举自动合成序列化代码。
例如,下面的Video 枚举现在可以被做成Codable ,而不需要我们的任何自定义代码:
enum Video: Codable {
case youTube(id: String)
case vimeo(id: String)
case hosted(url: URL)
}
为了看看上述类型在编码时是什么样子的,让我们创建一个VideoCollection 的实例,该实例存储一个Video 的值数组:
struct VideoCollection: Codable {
var name: String
var videos: [Video]
}
let collection = VideoCollection(
name: "Conference talks",
videos: [
.youTube(id: "ujOc3a7Hav0"),
.vimeo(id: "234961067")
]
)
如果我们再将上述collection 值编码为JSON,那么我们会得到以下结果:
{
"name": "Conference talks",
"videos": [
{
"youTube": {
"id": "ujOc3a7Hav0"
}
},
{
"vimeo": {
"id": "234961067"
}
}
]
}
所以,默认情况下,当我们让编译器自动为一个有关联值的枚举合成Codable 的一致性时,那么在计算该类型的序列化格式时,我们的案例名称和其中的关联值的标签将被使用。
对于没有标签的关联值,默认将使用下划线的数字键(如_0,_1, 等等)。
键的自定义
就像在处理结构体和类时一样,我们能够自定义在对枚举的情况和关联值进行编码或解码时将使用的键,我们甚至可以使用这种能力来完全省略某些情况。
例如,假设我们想扩展我们的Video 枚举以增加对本地视频的支持,但我们没有合理的方法来序列化这些视频的数据。
虽然我们总是可以创建一个完全独立的类型来表示这些视频,但我们也可以使用一个嵌套的CodingKeys 枚举来告诉编译器在生成我们的Video 类型的Codable 实现时忽略local 。
在这里,我们就是这样做的,而且我们还定制了hosted 的情况,以便在序列化时将其称为custom :
enum Video: Codable {
case youTube(id: String)
case vimeo(id: String)
case hosted(url: URL)
case local(LocalVideo)
}
extension Video {
enum CodingKeys: String, CodingKey {
case youTube
case vimeo
case hosted = "custom"
}
}
如果需要的话,我们甚至可以在一个特定的案例中自定义用于相关值的键。例如,我们可以这样声明,我们希望youTube 案例的id 值被序列化为youTubeID 。
extension Video {
enum YouTubeCodingKeys: String, CodingKey {
case id = "youTubeID"
}
}
上面的YouTubeCodingKeys 枚举通过名称与我们的youTube 案例相匹配,所以如果我们还想定制vimeo 案例,那么我们可以为其添加一个VimeoCodingKeys 枚举。
总结
尽管Codable的自动合成确实有其局限性(尤其是在处理与我们想在Swift类型中组织数据的方式大相径庭的序列化格式时),但它非常方便--而且在处理本地存储的值时往往特别有用,比如在磁盘上缓存值,或者在读取捆绑的配置文件时。
不管怎么说,带有关联值的枚举现在可以加入自动合成的行列了,这在一致性方面绝对是件好事,而且应该会在许多不同的代码库中证明是非常有用的--包括我自己的。
谢谢你的阅读!