一、Codable
Codable 是在 Swift4.0 时开始被引入的新特性,类似 Objective-c 的 NSCoding 和 NSSecureCoding,Codable 的定义如下:
typealias Codable = Decodable & Encodable
它是 Decodable 和 Encodable 协议的类型别名。当 Codable 用作类型或泛型约束时,它匹配符合这两种协议的任何类型。而 Decodable 和 Encodable 是协议类型,通过名称可以看出来,它们是用作编码和解码的,所以其实和 Objective-c 的 NSCoding 和 NSSecureCoding 功能类似。
Decodable 和 Encodable 的定义如下:
/// A type that can encode itself to an external representation.
public protocol Encodable {
/// Encodes this value into the given encoder.
///
/// If the value fails to encode anything, `encoder` will encode an empty
/// keyed container in its place.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
func encode(to encoder: Encoder) throws
}
/// A type that can decode itself from an external representation.
public protocol Decodable {
/// Creates a new instance by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
init(from decoder: Decoder) throws
}
如果你的类型只需单独支持编码或者解码的,只需要遵守 Decodable 或 Encodable 协议。要同时支持编码和解码,可以直接遵守 Codable 协议。 在 Swift 标准库中,很多类型都遵守 Codable 协议,例如:String、Int、Double、Date、Data、Array、Dictionary 以及 Optional。
1. 自动编码和解码
下面是一个表示地标名和创建年份的类型:Landmark。
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark now supports the Codable methods init(from:) and encode(to:),
// even though they aren't written as part of its declaration.
}
只要它遵守 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 协议,所以当有相关的内嵌类型的时候,我们可以这么写:
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
// Landmark is still codable after adding these properties.
var vantagePoints: [Coordinate]
var metadata: [String: String]
var website: URL?
}
单独的编码或者解码这里就不举例了,我们直接来看下一个东西:CodingKeys。
2. CodingKeys
Codable 类型可以声明一个名为 CodingKeys 的特殊嵌套枚举,该枚举符合 CodingKey 协议。当存在此枚举时,其案例将用作可编码类型的实例编码或解码时必须包含的属性的权威列表。枚举案例的名称应与您为类型中的相应属性指定的名称相匹配。
如果解码实例时不存在属性,或者某些属性不应包含在编码表示中,请从 CodingKeys 枚举中省略属性。CodingKeys 中省略的属性需要一个默认值,以便其包含类型自动符合 Decodable 或 Codable。
如果序列化数据格式中使用的键与数据类型中的属性名称不匹配,请通过指定 String 作为 CodingKeys 枚举的原始值类型来提供替代键。作为每个枚举案例的原始值使用的字符串是编码和解码期间使用的密钥名。
上面这段话虽然有点长,但我是从官方的文档抄过来然后翻译的。在开发的时候会经常与后台交互,但往往后台返回的字段名字有些是非常奇怪的,一个是命名规则,一个是命名的含义。如果不想和后台的命名一致,希望自己使用的时候更加直观,我们就可以使用 CodingKeys 来处理。
官方的例子如下:
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
}
}
通过 CodingKeys 枚举就可以将 JSON 中的 title 和 founding_date 字段替换成我们自定义的 name 和 foundingYear。
后面的 location 和 vantagePoints 为什么要写呢,是因为如果序列化数据格式中使用的键与数据类型中的属性名称不匹配,需要指定 String 作为枚举的原始值,否则编译器会报错的。
例如上面的例子,使用 CodingKeys 改变编码属性的名称后:
Landmark 解码的结果为:
{"name":"xxx","foundingYear":xxx, location:xxx, vantagePoints:xxx}。
Landmark 编码的结果为:
{"title":"xxx","founding_date":xxx,location:xxx, vantagePoints:xxx}
3. 手动编码和解码
前面已经了解了自动编码和解码,那么其实我们也可以自己手动的去编码和解码。我们引入一个官方的例子:
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
}
}
第一次看到这个例子你可能会有点懵,不知道 AdditionalInfoKeys 枚举是用来干什么的。先来看一段 JSON 的结构:
{
"latitude" : xxx
"longitude": xxx
"additionalInfo": {
"elevation" : xxx
"elevation2": xxx
"elevation3": xxx
......
}
}
这段 JSON 数据最外层的有三个字段,latitude、longitude 以及 additionalInfo,并且 additionalInfo 内部还有字段,所以 Coordinate 内部的 CodingKeys 的结构是这样的:
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}
这个时候你应该对 CodingKeys 有一个更清晰的理解了,CodingKeys 本质上是用来描述 JSON 中的 key 的。那么对于 additionalInfo 来说,我们可能只需要其中的 elevation,所以就有了 AdditionalInfoKeys:
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
所以,可以认为,AdditionalInfoKeys 就是用来 additionalInfo 内部的 key 的。此时在 Coordinate 中,我们就可以直接用 additionalInfo 中的 elevation 作为 Coordinate 的属性。
在了解 Coordinate 内部构成的含义之后,我们来看一下如何手动的编码和解码。首先来看解码:
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)
}
}
首先利用 extension 对 Coordinate 进行扩展并且遵守 Decodable 协议,然后实现协议的 init(from:) 方法,我们来看一下 init(from:) 中使用的 API 的含义。
-
container(keyedBy:):获取 CodingKey 的对应关系。
-
decode(:forKey:):解析单个属性。
-
nestedContainer(keyedBy:forKey:):获取内嵌的层级。
接下来看一下手动编码:
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)
}
}
其实也很简单,相对于手动解码,只是调用的方法不一样,但这个过程、步骤是一样的。
二、编码器与解码器
通过了解 Codable 我们已经知道,只要遵守了 Codable 协议,这个类型就支持编码和解码。而 JSONEncoder 和 JSONDecoder 就是用来进行编码和解码的。它们都是一个 class 类型。
1. JSONDecoder
现在看一下 JSONDecoder 是如何解码的,取的也是一个官方的例子:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let json = """
{
"name": "Durian",
"points": 600,
"description": "A fruit with a distinctive scent."
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)
print(product.name) // Prints "Durian"
通过生成 JSONDecoder 对象,调用对象的 decode(:from:) 方法来进行解码,需要注意的是第一个参数传的是支持 Decodable 协议的类型,第二个传的是一个 Data 类型,这个和我们平时用的第三方字典转模型不太一样。
2. JSONEncoder
使用 JSONEncoder 对象进行编码也是非常简单的,同样是官方的例子:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/
三、源码浅析
了解 Codable 以及 JSONDecoder、JSONEncoder 之后,接下来通过源码浅浅的分析一下它们的工作原理。目前最新的 Swift 源码时不包含 JSONDecoder、JSONEncoder 的源码的,只有 Codable 源码,Apple 把这两个类的源码放在另一个工程:swift-corelibs-foundation。
所以接下来需要用到两份源码: swift-corelibs-foundation,swift 源码。
1. JSONDecoder 源码浅析
1.1 JSONParser 与 JSONValue
首先打开 swift-corelibs-foundation 的源码,找到 JSONDecoder.swift 文件,先来看 decode(:from:) 方法的实现:
open func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
do {
// 生成 JSON 解析器,内部会用一个 Array 来存储 data
var parser = JSONParser(bytes: Array(data))
// 开始解析,生成 JSONValue
let json = try parser.parse()
// 生成 JSONDecoderImpl,由 JSONDecoderImpl 将 JSONValue 转成 Decodable 对象。
return try JSONDecoderImpl(userInfo: self.userInfo, from: json, codingPath: [], options: self.options).unwrap(as: T.self)
} catch let error as JSONError {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
} catch {
throw error
}
}
通过源码得知,开发者传入的 data 会被包装成 JSONParser 解析器,通过 JSONParser 对 JSON 数据进行解析,它的初始化方法非常简单:
init(bytes: [UInt8]) {
self.reader = DocumentReader(array: bytes)
}
DocumentReader 本身是对 bytes 的一个操作,我们直接看 JSONParser 的 parse 方法:
mutating func parse() throws -> JSONValue {
try reader.consumeWhitespace()
let value = try self.parseValue()
#if DEBUG
defer {
guard self.depth == 0 else {
preconditionFailure("Expected to end parsing with a depth of 0")
}
}
#endif
// ensure only white space is remaining
var whitespace = 0
while let next = reader.peek(offset: whitespace) {
switch next {
case ._space, ._tab, ._return, ._newline:
whitespace += 1
continue
default:
throw JSONError.unexpectedCharacter(ascii: next, characterIndex: reader.readerIndex + whitespace)
}
}
return value
}
这里调用 DocumentReader 的 consumeWhitespace,consumeWhitespace 方法的实现如下:
mutating func moveReaderIndex(forwardBy offset: Int) {
self.readerIndex += offset
}
mutating func consumeWhitespace() throws -> UInt8 {
var whitespace = 0
while let ascii = self.peek(offset: whitespace) {
switch ascii {
case ._space, ._return, ._newline, ._tab:
whitespace += 1
continue
default:
self.moveReaderIndex(forwardBy: whitespace)
return ascii
}
}
throw JSONError.unexpectedEndOfFile
}
我们再来看一下 ._space, ._return, ._newline, ._tab 都是些什么:
internal static let _space = UInt8(ascii: " ")
internal static let _return = UInt8(ascii: "\r")
internal static let _newline = UInt8(ascii: "\n")
internal static let _tab = UInt8(ascii: "\t")
所以,consumeWhitespace 其实是将空格、换行等对应的字符做一个过滤,调用 moveReaderIndex 方法拿到非将空格、换行的第一个 readerIndex。然后调用 parseValue 方法将 bytes 解析成 JSONValue。
JSONValue 是一个枚举,可以用来表示 String、Bool、数组、字典等类型:
enum JSONValue: Equatable {
case string(String)
case number(String)
case bool(Bool)
case null
case array([JSONValue])
case object([String: JSONValue])
}
此时再来看 parseValue 方法的实现:
mutating func parseValue() throws -> JSONValue {
var whitespace = 0
while let byte = reader.peek(offset: whitespace) {
switch byte {
case UInt8(ascii: "\""):
reader.moveReaderIndex(forwardBy: whitespace)
return .string(try reader.readString())
case ._openbrace:
reader.moveReaderIndex(forwardBy: whitespace)
let object = try parseObject()
return .object(object)
case ._openbracket:
reader.moveReaderIndex(forwardBy: whitespace)
let array = try parseArray()
return .array(array)
case UInt8(ascii: "f"), UInt8(ascii: "t"):
reader.moveReaderIndex(forwardBy: whitespace)
let bool = try reader.readBool()
return .bool(bool)
case UInt8(ascii: "n"):
reader.moveReaderIndex(forwardBy: whitespace)
try reader.readNull()
return .null
case UInt8(ascii: "-"), UInt8(ascii: "0") ... UInt8(ascii: "9"):
reader.moveReaderIndex(forwardBy: whitespace)
let number = try self.reader.readNumber()
return .number(number)
case ._space, ._return, ._newline, ._tab:
whitespace += 1
continue
default:
throw JSONError.unexpectedCharacter(ascii: byte, characterIndex: self.reader.readerIndex)
}
}
throw JSONError.unexpectedEndOfFile
}
在这个方法当中调用了很多的方法对 bytes 进行解析,例如:reader.readString()、reader.readBool()、parseArray() 等,代码太多了,我也大概看了一下,看不懂。
但此时我们应该知道,JSONDecoder 内部并不会通过 JSONSerialization 对 data 进行反序列化的操作。在 parse 方法中有一个 while 循环,这个不重要,到这里 parse 方法的浅析就结束了。
继续回到 decode(:from:) 方法,解析生成 JSONValue 之后,由 JSONDecoderImpl 来转成开发者需要的 Decodable 对象。
1.2 JSONDecoderImpl
JSONDecoderImpl 的定义如下:
fileprivate struct JSONDecoderImpl {
let codingPath: [CodingKey]
let userInfo: [CodingUserInfoKey: Any]
let json: JSONValue
let options: JSONDecoder._Options
init(userInfo: [CodingUserInfoKey: Any], from json: JSONValue, codingPath: [CodingKey], options: JSONDecoder._Options) {
self.userInfo = userInfo
self.codingPath = codingPath
self.json = json
self.options = options
}
}
可以看到,它的初始化方法非常的简单,只是记录一下外部的参数,注意!JSONDecoderImpl 是有遵守 Decoder 协议的,这点一定要注意,在后面的会有解释。
我们直接看到 unwrap(:) 方法:
func unwrap<T: Decodable>(as type: T.Type) throws -> T {
if type == Date.self {
return try self.unwrapDate() as! T
}
if type == Data.self {
return try self.unwrapData() as! T
}
if type == URL.self {
return try self.unwrapURL() as! T
}
if type == Decimal.self {
return try self.unwrapDecimal() as! T
}
if T.self is _JSONStringDictionaryDecodableMarker.Type {
return try self.unwrapDictionary(as: T.self)
}
return try T(from: self)
}
在这个方法中,无非就是根据 T.Type 来针对转成相应的类型做的处理,至于 unwrapDate、unwrapData 等方法这里就不去看了,我们直接看到最后一个返回的调用:
return try T(from: self)
到这里,JSONDecoder 的源码浅析就结束了,在最后调用的方法其实是 Decodable 的 init(from:) 方法:
init(from decoder: Decoder) throws
2. Decodable 源码浅析
通过对 JSONDecoder 源码的浅析,已经得知,除了几个特殊的类型外,最后走的都是遵守 Decodable 的 init(from:) 方法,那在 swift 的 Decodable 源码中,是没有针对 T(from: self) 的实现的。所以,我们只能通过底层的 sil 的代码去窥探 T(from: self) 的实现。
如果不了解 sil 的可以通过《方法》 这篇文章去了解。代码如下:
struct Person: Decodable {
var nickname: String
var age: Int
}
生成 sil 之后直接找到 Person.init(from:) 的实现,一般来讲这个初始化方法上方都会有一个注释告诉你该方法的名称,例如:
// Person.init(from:)
sil hidden @$s4main6PersonV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Person.Type) -> (@owned Person, @error Error) {
......
由于代码过长,就不一一的贴出来了,很多代码我都是直接忽略的,我们直接看关键的,如下:
// Person.init(from:)
sil hidden @$s4main6PersonV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Person.Type) -> (@owned Person, @error Error) {
// %0 "decoder" // users: %69, %49, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin Person.Type):
......
%8 = alloc_stack $KeyedDecodingContainer<Person.CodingKeys>, let, name "container", implicit // users: %45, %44, %37, %66, %65, %21, %60, %59, %13, %55
%9 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder // users: %13, %13, %12
%10 = metatype $@thin Person.CodingKeys.Type
%11 = metatype $@thick Person.CodingKeys.Type // user: %13
%12 = witness_method $@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error Error) // type-defs: %9; user: %13
try_apply %12<@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder, Person.CodingKeys>(%8, %11, %9) : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error Error), normal bb1, error bb4 // type-defs: %9; id: %13
}
先来看第一段代码 bb0,这一段代码第一次看的时候可能会有点懵逼,注意看关键的部分:%12 这行,我们看到 witness_method,如果看过我写的《协议的本质》 这篇文章,这其实是一个协议见证表的调度方法。
也就意味着 bb0 中主要调用的是 Decoder 协议的方法,也就是这个方法:
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
那这个方法是实现在哪里呢,还记得前面讲过 JSONDecoderImpl 是有遵守 Decoder 协议的么,在 JSONDecoderImpl 的 unwrap(:) 方法的最后一个返回值
return try T(from: self)
这个 self 不正是 JSONDecoderImpl 么,所以在 Person.init(from:) 中的 Decoder 参数,本质上就是 JSONDecoderImpl。
那么在 JSONDecoderImpl 中 container<Key>(keyedBy:) 方法的实现如下:
@usableFromInline func container<Key>(keyedBy _: Key.Type) throws ->
KeyedDecodingContainer<Key> where Key: CodingKey
{
guard case .object(let dictionary) = self.json else {
throw DecodingError.typeMismatch([String: JSONValue].self, DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Expected to decode \([String: JSONValue].self) but found \(self.json.debugDataTypeDescription) instead."
))
}
let container = KeyedContainer<Key>(
impl: self,
codingPath: codingPath,
dictionary: dictionary
)
return KeyedDecodingContainer(container)
}
所以 Person.init(from:) 方法中的第一行代码我们应该搞定了,不就是调用了 container<Key>(keyedBy:) 方法么,实现如下:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
......
}
此时,注意看 bb0 的最后一行,如果成功调用的是 bb1,我们接着看 bb1 的代码:
bb1(%14 : $()): // Preds: bb0
%15 = metatype $@thin String.Type // user: %21
%16 = metatype $@thin Person.CodingKeys.Type
%17 = enum $Person.CodingKeys, #Person.CodingKeys.nickname!enumelt // user: %19
%18 = alloc_stack $Person.CodingKeys // users: %19, %23, %21, %58
store %17 to %18 : $*Person.CodingKeys // id: %19
// function_ref KeyedDecodingContainer.decode(_:forKey:)
%20 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2Sm_xtKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin String.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@owned String, @error Error) // user: %21
try_apply %20<Person.CodingKeys>(%15, %18, %8) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin String.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@owned String, @error Error), normal bb2, error bb5 // id: %21
这一段比较简单,人家连注释都给你了,不就是调用 KeyedDecodingContainer 的 decode(_:forKey:) 方法么,并且成功之后跳转到 bb2,我们再来看 bb2 的实现:
// %22 // users: %63, %48, %46, %29, %28
bb2(%22 : $String): // Preds: bb1
dealloc_stack %18 : $*Person.CodingKeys // id: %23
%24 = begin_access [modify] [static] %3 : $*Person // users: %30, %25
%25 = struct_element_addr %24 : $*Person, #Person.nickname // user: %29
%26 = integer_literal $Builtin.Int2, 1 // user: %27
store %26 to %2 : $*Builtin.Int2 // id: %27
retain_value %22 : $String // id: %28
store %22 to %25 : $*String // id: %29
end_access %24 : $*Person // id: %30
%31 = metatype $@thin Int.Type // user: %37
%32 = metatype $@thin Person.CodingKeys.Type
%33 = enum $Person.CodingKeys, #Person.CodingKeys.age!enumelt // user: %35
%34 = alloc_stack $Person.CodingKeys // users: %35, %39, %37, %64
store %33 to %34 : $*Person.CodingKeys // id: %35
// function_ref KeyedDecodingContainer.decode(_:forKey:)
%36 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2im_xtKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin Int.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (Int, @error Error) // user: %37
try_apply %36<Person.CodingKeys>(%31, %34, %8) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin Int.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (Int, @error Error), normal bb3, error bb6 // id: %37
和 bb1 基本是一样的,最终也是调用 KeyedDecodingContainer 的 decode(_:forKey:) 方法,并且成功之后跳转到 bb3,bb3 我们就不去看了,看到这里已经够了。
此时我们基本可以直接得出 Person.init(from:) 的实现,大概实现如下:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nickname = try container.decode(String.self, forKey: .nickname)
age = try container.decode(Int.self, forKey: .age)
}
当然,你要复制到自己的代码肯定是会报错的,因为这是底层的实现,报错了自己改改就行,此时就会发现这里和前面的手动编码是一样的。
3. KeyedDecodingContainer 源码浅析
KeyedDecodingContainer 是一种具体的容器,提供解码器存储器的视图,使可解码类型的编码属性可由键访问。其的定义如下:
/// A concrete container that provides a view into a decoder's storage, making
/// the encoded properties of a decodable type accessible by keys.
public struct KeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K : CodingKey {
// 更多实现
......
注意看,KeyedDecodingContainer 遵守 KeyedDecodingContainerProtocol 协议,KeyedDecodingContainerProtocol 中声明了非常多的计算属性和方法,而方法的实现是由 KeyedDecodingContainer 实现的,内部是如何实现的呢。
这里拿其中的一个方法做例子:
public func decode<T: Decodable>(
_ type: T.Type,
forKey key: Key
) throws -> T {
return try _box.decode(T.self, forKey: key)
}
可以看到,内部通过一个 _box 属性调用了 decode<T>(:forKey) 方法,接着来看 _box 是什么:
/// The container for the concrete decoder.
internal var _box: _KeyedDecodingContainerBase
这是一个用来描述解码器的容器,在源码中找到 _KeyedDecodingContainerBase,发现它只是一个基类,真正的实现是由子类实现的,其子类如下:
internal final class _KeyedDecodingContainerBox<
Concrete: KeyedDecodingContainerProtocol
>: _KeyedDecodingContainerBase {
typealias Key = Concrete.Key
internal var concrete: Concrete
internal init(_ container: Concrete) {
concrete = container
}
// 更多实现
......
注意看这个 Concrete,Concrete 遵守 KeyedDecodingContainerProtocol 协议,这个地方可能有点绕。我们回到 KeyedDecodingContainer,先来看看 KeyedDecodingContainer 的初始化方法:
/// Creates a new instance with the given container.
///
/// - parameter container: The container to hold.
public init<Container: KeyedDecodingContainerProtocol>(
_ container: Container
) where Container.Key == Key {
_box = _KeyedDecodingContainerBox(container)
}
可以看到这个初始化方法要求传一个遵守 KeyedDecodingContainerProtocol 协议的 Container 类型,然后再传给 _KeyedDecodingContainerBox。那这个 Container 其实在是在 JSONDecoderImpl 中的 container<Key>(keyedBy:) 方法
在 JSONDecoderImpl 中 container<Key>(keyedBy:) 方法有提到,Container 本质上是一个 KeyedContainer<K: CodingKey> 结构体,其定义如下:
struct KeyedContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
typealias Key = K
// 更多实现
......
在这里不要被弄晕了,虽然在两份源码之间跳来跳去,但仔细阅读的时候还是能理解的。
我们接着回到 _KeyedDecodingContainerBox,来看 decode<T>(:forKey) 在 _KeyedDecodingContainerBox 中是如何实现的:
override internal func decode<T: Decodable, K: CodingKey>(
_ type: T.Type,
forKey key: K
) throws -> T {
_internalInvariant(K.self == Key.self)
let key = unsafeBitCast(key, to: Key.self)
return try concrete.decode(T.self, forKey: key)
}
此时你会发现,在做一个类型的强制转换之后,调用的是 concrete 的 decode<T>(:forKey) 方法,而这个 concrete 不正是前面讲的遵守 KeyedDecodingContainerProtocol 协议的 KeyedContainer 类型吗,此时我们来看看 KeyedContainer 又是如何实现 decode<T>(:forKey) 方法的:
func decode<T>(_: T.Type, forKey key: K) throws -> T where T: Decodable {
let newDecoder = try decoderForKey(key)
return try newDecoder.unwrap(as: T.self)
}
首先拿到一个新的解码器,通过解码器调用 unwrap(as:) 方法进行解码,如何获得解码器的呢?继续看 decoderForKey(:) 方法的实现:
private func decoderForKey<LocalKey: CodingKey>(_ key: LocalKey) throws -> JSONDecoderImpl {
let value = try getValue(forKey: key)
var newPath = self.codingPath
newPath.append(key)
return JSONDecoderImpl(
userInfo: self.impl.userInfo,
from: value,
codingPath: newPath,
options: self.impl.options
)
}
@inline(__always) private func getValue<LocalKey: CodingKey>(forKey key: LocalKey) throws -> JSONValue {
guard let value = dictionary[key.stringValue] else {
throw DecodingError.keyNotFound(key, .init(
codingPath: self.codingPath,
debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\")."
))
}
return value
}
所以最终又回到了 JSONDecoderImpl,所以,JSONDecoder 去解析一个非标准库中遵守 Decodable 协议类型的时候,大致的流程基本就是这样的。
最后附上一张流程图:
四、解码容器
-
KeyedDecodingContainer:提供解码器存储视图的具体容器,使可解码类型的编码属性可通过键访问。
-
UnkeyedDecodingContainer:一种提供解码器存储视图的类型,用于按顺序保存可解码类型的编码属性,无需密钥。解码器应为其格式提供符合
UnkeyedDecodingContainer的类型。 -
SingleValueDecodingContainer:一个容器,可以支持单个非键值的存储和直接解码。
文字描述的区别可能有点干,下面通过一段伪代码来描述:
// KeyedDecodingContainer,通过字典key来取值
let value = container[key]
// UnkeyedDecodingContainer,通过数组下标来取值
let value = container[index]
// SingleValueDecodingContainer,value 就是 container 本身
let value = container
1. KeyedDecodingContainer 应用示例
struct Person: Decodable {
var nickname: String
var age: Int
private enum CodingKeys: String, CodingKey {
case nickname = "name"
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nickname = try container.decode(String.self, forKey: .nickname)
age = try container.decode(Int.self, forKey: .age)
}
}
let jsonString = """
{
"name": "Tom",
"age": 23,
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let jsonData = jsonData,
let result = try? decoder.decode(Person.self, from: jsonData) {
print(result)
} else {
print("解析失败")
}
当 jsonString 能被序列化成字典的时候,就应该选择 KeyedDecodingContainer 作为 解码容器。
2. UnkeyedDecodingContainer 应用示例
这是一个 Point:
struct Point: Decodable {
var x: Double
var y: Double
}
此时服务器返回的 json 数据不是一个字典,而是一个数组:
let jsonString = "[10,20]"
此时就不能使用 KeyedDecodingContainer 去解析了,而是用 UnkeyedDecodingContainer,代码如下:
struct Point: Decodable {
var x: Double
var y: Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
x = try container.decode(Double.self)
y = try container.decode(Double.self)
}
}
如果你的 JSON 数据能被序列化成数组,那么可以用 UnkeyedDecodingContainer 来解析,至于 UnkeyedDecodingContainer 的源码,感兴趣的靓仔靓女们可以参照前面 KeyedDecodingContainer 的源码分析的方式对 UnkeyedDecodingContainer 的源码进行阅读。
3. SingleValueDecodingContainer 应用示例
假设,服务器返回一个字段,可能是 Int 类型,可能是 String 类型,虽然这种情况比较少,但还是有些后台会这么给数据 😓。
我们可以自己同时支持 String 和 Int 的类型:
struct StringInt: Codable {
var stringValue: String
var intValue: Int
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
stringValue = value
intValue = Int(value) ?? 0
} else if let value = try? container.decode(Int.self) {
stringValue = "\(value)"
intValue = value
} else {
stringValue = ""
intValue = 0
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if !stringValue.isEmpty {
try? container.encode(stringValue)
} else {
try? container.encode(intValue)
}
}
}
extension StringInt: ExpressibleByStringLiteral {
init(stringLiteral value: StringLiteralType) {
stringValue = value
intValue = Int(value) ?? 0
}
init(unicodeScalarLiteral value: String) {
stringValue = value
intValue = Int(value) ?? 0
}
init(extendedGraphemeClusterLiteral value: String) {
stringValue = value
intValue = Int(value) ?? 0
}
}
extension StringInt: ExpressibleByIntegerLiteral {
init(integerLiteral value: IntegerLiteralType) {
stringValue = "\(value)"
intValue = value
}
}
当我们拿到的数据可能是 Int 类型,可能是 String 类型的时候,我们可以通过 SingleValueDecodingContainer 分别尝试对 String.self 和 Int.self 进行解析。
五、使用 Property Wrapper 为 Codable 解码设定默认值
当你看到这儿的时候,基本上了解如何使用 Codable 进行编码和解码了,但其实在开发中我们不免会遇到一些问题,例如服务器返回的某个字段可能会为 nil 或者甚至不给,这个时候我们需要在模型中加上可选项,但在取一个可选值的时候我们需要进行解包,然后就会有非常多的 if else,写起来费劲,看起来难受。
针对这个问题,我自己写一份使用 Property Wrapper 为 Codable 解码设定默认值的代码,如下:
// 拥有 DefaultValue 协议和 Codable 协议的组合协议,目的是使 SingleValueDecodingContainer 的 decode 保证语法上正确!
typealias DefaultCodableValue = DefaultValue & Codable
// 属性包装器
@propertyWrapper
struct Default<T: DefaultCodableValue> {
var wrappedValue: T
}
// 包装器遵守 Codable 协议,实现默认的 decoder 和 encoder 方法
extension Default: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
// 对于 key 不存在或者意外输入不同类型时使用默认的值
extension KeyedDecodingContainer {
func decode<T>(
_ type: Default<T>.Type,
forKey key: Key
) throws -> Default<T> where T: DefaultCodableValue {
if let value = try decodeIfPresent(type, forKey: key) {
return value
} else {
return Default(wrappedValue: T.defaultValue)
}
}
}
// 数组相关的处理,含义和 KeyedDecodingContainer 的处理一样
extension UnkeyedDecodingContainer {
mutating func decode<T>(
_ type: Default<T>.Type
) throws -> Default<T> where T : DefaultCodableValue {
try decodeIfPresent(type) ?? Default(wrappedValue: T.defaultValue)
}
}
// 默认值协议
protocol DefaultValue {
static var defaultValue: Self { get }
}
// 可以给某个可能为 nil 的类型遵守 DefaultValue,使其拥有 defaultValue
extension Bool: DefaultValue { static let defaultValue = false }
extension String: DefaultValue { static var defaultValue = "" }
extension Int: DefaultValue { static var defaultValue = 0 }
extension Double: DefaultValue { static var defaultValue = 0.0 }
extension Float: DefaultValue { static var defaultValue: Float { 0.0 } }
extension Array: DefaultValue { static var defaultValue: Array<Element> { [] } }
extension Dictionary: DefaultValue { static var defaultValue: Dictionary<Key, Value> { [:] } }
在此还要感谢喵神的一篇文章:使用 Property Wrapper 为 Codable 解码设定默认值。