OverView
许多编程任务涉及通过网络连接发送数据,将数据保存到磁盘或将数据提交到API和服务。 这些任务通常要求在传输数据时将数据编码和解码为中间格式。
Swift标准库定义了数据编码和解码的标准化方法。 您可以通过在自定义类型上实现Encodable和Decodable协议来使用此方法。 遵循这些协议,编码器和解码器协议的实现会被允许获取您的数据,并将其编码或解码为外部表示形式(如JSON或属性列表)。 为了支持编码和解码,需要遵守Codable协议,Codable结合了Encodable和Decodable协议。 这个过程被称为使您的类型可编码。
Encode and Decode Automatically
使类型可编码的最简单方法是使用已经可编码的类型声明其属性。 这些类型包括标准库类型,如String,Int和Double; 和基础类型,如日期,数据和URL。 如果一个类型的属性类型都遵守了Codable协议,那么要让这个类型可编码,只需要遵守Codable协议。
考虑一个“Landmark”结构,它存储地标的名称和创始年份:
struct Landmark {
var name: String
var foundingYear: Int
}
将Codable添加到Landmark的继承列表会触发满足Encodable和Decodable的所有协议要求的自动一致性:
ps: 即只要遵守了Codable协议,相当于同时遵守了Encodable 协议和Decodable协议。
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark 现在支持 Codable 协议的方法 init(from:) 和 encode(to:),
// 尽管它们并没有显式写到声明中
}
在您自己的类型上采用Codable使您可以将它们序列化为任何内置数据格式,以及自定义编码器和解码器提供的任何格式。 例如,Landmark结构可以使用PropertyListEncoder和JSONEncoder类进行编码,即使Landmark本身不包含专门处理属性列表或JSON的代码。
同样的原则适用于由可编码的其他自定义类型组成的自定义类型。 只要它的所有属性都是Codable,任何自定义类型也可以是Codable。
下面的示例显示了将位置属性添加到Landmark结构时如何应用自动Codable一致性:
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
struct Landmark: Codable {
// Double, String, and Int all conform to Codable.
var name: String
var foundingYear: Int
// Adding a property of a custom Codable type maintains overall Codable conformance.
var location: Coordinate
}
内置类型(如Array,Dictionary和Optional)在包含可编码类型时也符合Codable。 您可以向Landmark添加一个Coordinate实例数组,整个结构仍将满足Codable。
下面的示例显示了在Landmark中使用内置可编码类型添加多个属性时,自动一致性如何仍然适用:
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
// Landmark在添加这些属性后仍然可编码
var vantagePoints: [Coordinate]
var metadata: [String: String]
var website: URL?
}
Encode or Decode Exclusively
在某些情况下,您可能不需要Codable支持双向编码和解码。 例如,某些应用程序只需要调用远程网络API,而不需要解码包含相同类型的响应。 如果您只需要支持数据编码,则声明符合Encodable。 相反,如果您只需要读取给定类型的数据,则声明符合Decodable。
以下示例显示了仅对数据进行编码或解码的Landmark结构的替代声明:
struct Landmark: Encodable {
var name: String
var foundingYear: Int
}
struct Landmark: Decodable {
var name: String
var foundingYear: Int
}
Choose Properties to Encode and Decode Using Coding Keys
Codable类型可以声明一个名为CodingKeys的特殊嵌套枚举,它符合CodingKey协议。当存在此枚举时,其case充当属性权威列表,在编码或解码可编码类型的实例时该属性必须包含在内。枚举case的名称应与您为类型中的相应属性指定的名称相匹配。
如果在解码实例时它们不存在,或者如果某些属性不应包含在编码表中,则忽略CodingKeys枚举中的属性。 CodingKeys中省略的属性需要一个默认值,以使其包含类型能够接收与Decodable或Codable的自动一致性。
如果序列化数据格式中使用的键与数据类型中的属性名称不匹配,请通过将String指定为CodingKeys枚举的原始值类型来提供备用键。用作每个枚举情况的原始值的字符串是在编码和解码期间使用的键名。case名称与其原始值之间的关联使您可以根据Swift API设计指南命名数据结构,而不必匹配您正在建模的序列化格式的名称,标点符号和大小写。
以下示例在编码和解码时使用替代键作为Landmark结构的name和foundingYear属性:
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
var vantagePoints: [Coordinate]
enum CodingKeys: String, CodingKey {
case name = "title"
case foundingYear = "founding_date"
case location
case vantagePoints
}
}
Encode and Decode Manually
如果你的Swift类型的结构与其编码形式的结构不同,则可以提供Encodable和Decodable的自定义实现来定义自己的编码和解码逻辑。
在下面的示例中,扩展了Coordinate结构以支持嵌套在additionalInfo容器内的提升属性:
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}
因为Coordinate类型的编码形式包含第二级嵌套信息,所以类型采用Encodable和Decodable协议使用两个枚举,每个枚举列出在特定级别上使用的完整编码密钥集。
在下面的示例中,通过实现其所需的初始化程序init(from :),扩展了Coordinate结构以符合Decodable协议:
extension Coordinate: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
}
}
初始化程序通过使用参数Decoder实例上的方法来填充Coordinate实例。 Coordinate实例的两个属性使用Swift标准库提供的键控容器API进行初始化。
下面的示例显示了如何通过实现其所需的方法encode(to:)来扩展Coordinate结构以符合Encodable协议:
extension Coordinate: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(elevation, forKey: .elevation)
}
}
encode(to :)方法的这种实现颠倒了前一个例子的解码操作。
有关自定义编码和解码过程时使用的容器类型的更多信息,请参阅KeyedEncodingContainerProtocol和UnkeyedEncodingContainer。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。