源码解读——Codable
Swift 4.0 支持了一个新的语言特性——Codable,其提供了一种非常简单的方式支持模型和数据之间的转换。
关于 Codable,今年年初的时候,我在调研如何让 Codable 在数据转模型时支持默认值,为了能够对 Codable 有个整体印象,我简单阅读了一下源码。当时感觉 Codable 的源码涉及到的类非常多,而且不同的类似乎实现了同样的协议,方法传来传去地执行,整体给人的感觉有点懵。
直到最近,在研究了 Swift 泛型协议和类型擦除后,我对 Codable 逐渐有了一个清晰的认知。本文,则记录了我在阅读 Codable 源码后对于它的一些理解。
本文阅读的 Codable 源码文件主要包含两个文件: - Codable.swift - JSONEncoder.swift
这两个文件中定义了非常多的类,为了能够对各个类有个清晰的认知,我为各个类(包括私有类)抽取了独立的文件,以便于阅读,代码传送门。
下文我们将从使用 Codable 进行 JSON 数据和模型转换的示例说起。
使用示例
通过 Codable,我们很容易就能实现 JSON
数据和自定义模型之间的相互转换,如下所示。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46struct Address: Codable {
var province: String
var city: String
}
struct Student: Codable {
var name: String
var age: Int
var address: Address
enum CodingKeys: String, CodingKey {
case name
case age
case address = "addr"
}
}
// Encode
print("============= Encode =============")
let address = Address(province: "Zhejiang", city: "Huzhou")
let student = Student(name: "baochuquan", age: 18, address: address)
let encoder = JSONEncoder()
guard
let data = try? encoder.encode(student),
let encodedString = String(data: data, encoding: .utf8)
else {
fatalError()
}
print(encodedString)
// 输出:
// {"name":"baochuquan","age":18,"addr":{"province":"Zhejiang","city":"Huzhou"}}
// Decode
print("============= Decode =============")
let jsonString = "{\"name\":\"baochuquan\",\"age\":18,\"addr\":{\"province\":\"Zhejiang\",\"city\":\"Huzhou\"}}"
let decoder = JSONDecoder()
guard
let jsonData = jsonString.data(using: .utf8),
let model = try? decoder.decode(Student.self, from: jsonData)
else {
fatalError()
}
print(model)
// 输出:
// Student(name: "baochuquan", age: 18, address: __lldb_expr_15.Address(province: "Zhejiang", city: "Huzhou"))Studuent
模型,其嵌套了一个 Address
模型。首先通过 encode 的方式将模型转换成 JSON,然后通过 decode 的方式将
JSON 转换成模型。
整体结构
Codable
的核心构成可以分为三个部分:
- Encodable & Decodable 协议
- Encoder & Decoder 协议
- Container 相关协议
Encodable & Decodable
Encodable
协议要求目标模型必须提供编码方法
func encode(from encoder: Encoder)
,从而按照指定的逻辑进行编码。
Decodable
协议要求目标模型必须提供解码方法
func init(from decoder: Decoder)
,从而按照指定的逻辑进行解码。
Codable
则正是两个协议的并集,如下所示为
Codable
协议的定义。 1
2
3
4
5
6
7
8
9
10
11protocol Encodable {
// 把值编码到 encoder 中
func encode(to encoder: Encoder) throws
}
protocol Decodable {
// 从 decoder 中把值解码出来
init(from decoder: Decoder) throws
}
typealias Codable = Decodable & Encodable
在使用示例中,我们可以看到,模型只要遵循了 Codable
协议就可以进行数据和模型的相互转换。不过,我们可能会产生一个疑问:Student
和 Address
类遵循了 Codable
协议,按道理应该需要去实现协议,为什么这里模型却没有实现协议?难道
Codable 协议存在默认实现?事实上,Codable 并没有默认实现,而是
编译器为遵循 Codable
协议的类自动生成了对应的实现方法。关于这一点,我们在下文还会进一步进行介绍。
Encoder & Decoder
Encoder
协议要求编码器必须提供 3 种类型的编码
container、编码路径、上下文缓存。
Decoder
协议要求编码器必须提供 3 中类型的解码
container、解码路径、上下文缓存。
协议的具体定义如下所示: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23protocol Encoder {
/// The path of coding keys taken to get to this point in encoding.
var codingPath: [CodingKey] { get }
/// Any contextual information set by the user for encoding.
var userInfo: [CodingUserInfoKey: Any] { get }
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key>
func unkeyedContainer() -> UnkeyedEncodingContainer
func singleValueContainer() -> SingleValueEncodingContainer
}
protocol Decoder {
/// The path of coding keys taken to get to this point in decoding.
var codingPath: [CodingKey] { get }
/// Any contextual information set by the user for decoding.
var userInfo: [CodingUserInfoKey: Any] { get }
func container<Key>(
keyedBy type: Key.Type
) throws -> KeyedDecodingContainer<Key>
func unkeyedContainer() throws -> UnkeyedDecodingContainer
func singleValueContainer() throws -> SingleValueDecodingContainer
}
对于 JSON 的编解码,swift 默认提供了两个私有类实现了
Encoder
协议和 Decoder
协议。
__JSONEncoder
:实现了Encoder
协议。__JSONDecoder
:实现了Decoder
协议。
Container
Codable 中包含三种类型的 container,由于还要同时支持编码和解码,因此总共有 6 种类型的 container,分别是:
KeyedEncodingContainer<Key>
KeyedDecodingContainer<Key>
UnkeyedEncodingContainer
UnkeyedDecodingContainer
SingleValueEncodingContainer
SingleValueDecodingContainer
Container 主要用于
缓存单个嵌套层级的编码/解码结果。例如,对于
Student
这样的嵌套模型(内部嵌套了
Address
),它需要两个 container,一个用于缓存
Student
层级的编码/解码结果,一个用于缓存
Address
层级的编码/解码结果。
本质上,编码/解码的过程是根据数据/模型的嵌套层级递归计算完成的,每一层递归都会有一个 container 用于保存其运算结果,最终组成一个对应嵌套层级的编码/解码结果。
如果不区分编码和解码,那么 Codable 有 3 种类型的 container,我们分别简单介绍一下。
KeyedContainer<Key>
KeyedContainer<Key>
主要用于带键数据的编解码,比如,使用
KeyedEncodingContainer<Key>
能够编码得到类似
{"name": "baochuquan", "age": 18}
这样的结果。KeyedContainer<Key>
是泛型类型,其类型参数 Key
就是遵循 CodingKey
协议的类型。KeyedContainer<Key>
是最常用的
container,上述例子中 Student
和 Address
都是基于 KeyedContainer<Key>
完成的。
下图所示分别是 KeyedEncodingContainer<Key>
和
KeyedDecodingContainer<Key>
相关的类图。在《Swift
类型擦除》文中,我们介绍了下面这种结构,它的主要作用是
对泛型协议进行类型擦除,从而能够实现泛型协议的存储。
其中,KeyedEncodingContainerProtocol
和
KeyedDecodingContainerProtocol
是泛型协议。协议的真正实现者是 __JSONKeyedEncodingContainer
和
__JSONKeyedDecodingContainer
,两者是泛型结构体。它们内部则分别调用了
__JSONEncoder
和 __JSONDecoder
内部的相关方法,从而完成编码和解码的核心逻辑。__JSONEncoder
和 __JSONDecoder
也正是 Encoder
协议和
Decoder
协议的实现者。
在图中,其他的类,包括:KeyedEncodingContainer<Key>
、_KeyedEncodingContainerBase
、_KeyedEncodingContainerBox
和
KeyedDecodingContainer<Key>
、_KeyedDecodingContainerBase
、_KeyedDecodingContainerBox
,它们内部对方法调用进行了层层转发,目的是辅助解决类型擦除问题。
UnkeyedContainer
UnkeyedContainer
主要用于不带键数据的编解码,比如,使用
UnkeyedEncodingContainer<Key>
能够编码得到类似
["baochuquan", 18]
这样的结果。既然是
Unkeyed
,那就是结果中只有 value,没有
key。总体来说,UnkeyedContainer
使用较少。
下图所示分别是 UnkeyedEncodingContainer
和
UnkeyedDecodingContainer
相关的类图。由于它们并不需要支持泛型,所以无需一堆辅助类来实现类型擦除。
其中,UnkeyedEncodingContainer
和
UnkeyedDecodingContainer
的真正实现者分别是
__JSONUnkeyedEncodingContainer
和
__JSONUnkeyedDecodingContainer
。它们内部则分别调用了
__JSONEncoder
和 __JSONDecoder
内部的相关方法,从而完成编码和解码的核心逻辑。
SingleValueContainer
SingleValueContainer
主要用于单个值数据的编解码,比如,使用
SingleEncodingContainer
能够编码得到类似
"baochuquan"
这样的结果。它只是一个值,并不是字典或数组。总体来说,用的也比较少。
下图所示分别是 SingleValueEncodingContainer
和
SingleValueDecodingContainer
的相关类图。同样,它们也不支持泛型,无需辅助类来实现类型擦除。
这里,SingleValueEncodingContainer
和
SingleValueDecodingContainer
的真正实现者直接就是
__JSONEncoder
和 __JSONDecoder
。
小结
从上述三种 container
的类图中我们可以发现,__JSONEncoder
和
__JSONDecoder
才是真正实现编码和解码的关键。
核心逻辑
接下来,我们从上述两个例子开始,研究一下 Codable 编码和解码的核心逻辑。
编码逻辑
如下所示,在编码过程中,首先会调用初始化一个 JSONEncoder
对象,然后调用其 encode(_:)
方法。 1
2
3
4
5
6
7let encoder = JSONEncoder()
guard
let data = try? encoder.encode(student),
let encodedString = String(data: data, encoding: .utf8)
else {
fatalError()
}
JSONEncoder
的 encode(_:)
方法内部逻辑如下所示,核心部分就是初始化一个 __JSONEncoder
对象,调用其 box_(_:)
方法。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func encode<T : Encodable>(_ value: T) throws -> Data {
let encoder = __JSONEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
}
let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)
do {
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
} catch {
throw EncodingError.invalidValue(value,
EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
}
}
__JSONEncoder
的 box_(_:as:)
方法,其内部逻辑如下所示。该方法内部会根据 type
的类型找到对应的 box
方法进行编码转换,如果是自定义类型,如
Student
,那么会走到最后的
value.encode(to: self)
,而这个方法就是
Encodable
协议所声明的方法。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40func box_(_ value: Encodable) throws -> NSObject? {
// Disambiguation between variable and function is required due to
// issue tracked at: https://bugs.swift.org/browse/SR-1846
let type = Swift.type(of: value)
if type == Date.self || type == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if type == Data.self || type == NSData.self {
// Respect Data encoding strategy
return try self.box((value as! Data))
} else if type == URL.self || type == NSURL.self {
// Encode URLs as single strings.
return self.box((value as! URL).absoluteString)
} else if type == Decimal.self || type == NSDecimalNumber.self {
// JSONSerialization can natively handle NSDecimalNumber.
return (value as! NSDecimalNumber)
} else if value is _JSONStringDictionaryEncodableMarker {
return try self.box(value as! [String : Encodable])
}
// The value should request a container from the __JSONEncoder.
let depth = self.storage.count
do {
try value.encode(to: self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}
return self.storage.popContainer()
}
但是,Student
类并没有实现 encode(to:)
方法,这是一个疑问。
解码逻辑
如下所示,在解码过程中,首先会调用初始化一个 JSONDecoder
对象,然后调用其 decode(_:from:)
方法。 1
2
3
4
5
6
7let decoder = JSONDecoder()
guard
let jsonData = jsonString.data(using: .utf8),
let model = try? decoder.decode(Student.self, from: jsonData)
else {
fatalError()
}
JSONDecoder
的 decode(_:from:)
方法内部逻辑如下所示,核心部分就是初始化一个 __JSONDecoder
对象,调用其 unbox(_:as:)
方法。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
__JSONDecoder
的 unbox(_:as:)
方法内部直接转发至核心方法
unbox_(_:as:)
,其内部逻辑如下所示。该方法内部会根据
type
的类型找到对应的 unbox
方法进行解码转换,如果是自定义类型,如
Student
,那么会走到最后的
type.init(from: self)
,而这个方法就是
Decodable
协议所声明的方法。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
if type == Date.self || type == NSDate.self {
return try self.unbox(value, as: Date.self)
} else if type == Data.self || type == NSData.self {
return try self.unbox(value, as: Data.self)
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url
} else if type == Decimal.self || type == NSDecimalNumber.self {
return try self.unbox(value, as: Decimal.self)
} else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
return try self.unbox(value, as: stringKeyedDictType)
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
但是,Student
类并没有实现 init(from:)
方法,这又是一个疑问。
SIL
上面,我们在追溯编码逻辑和解码逻辑中,抛出了两个问题:模型并没有实现 Codable 协议所声明的两个方法。我们推测可能是编译器生成的代码实现了这两个方法。
下面,我们可以通过如下命令,编译 Student
生成 SIL
来看看编译器到底做了什么额外的工作。 1
swiftc Student.swift -emit-sil > Student.sil
我们首先能看到编译器生成了 Student
和
Address
相关的代码,如下所示。对于未指定
CodingKey
的模型,编译器会自动生成一个嵌套类型,比如:Address
内部生成了一个枚举类型 CodingKeys
。对于未实现
Codable
的模型,编译器会自动生成对应的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39struct Address : Decodable & Encodable {
var province: String { get set }
var city: String { get set }
init(province: String, city: String)
enum CodingKeys : CodingKey {
case province
case city
Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Address.CodingKeys, _ b: Address.CodingKeys) -> Bool (
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
var stringValue: String { get }
init?(stringValue: String)
var intValue: Int? { get }
init?(intValue: Int)
}
init(from decoder: Decoder) throws
func encode(to encoder: Encoder) throws
}
struct Student : Decodable & Encodable {
var name: String { get set }
var age: Int { get set }
var address: Address { get set }
enum CodingKeys : String, CodingKey {
case name
case age
case address
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
var stringValue: String { get }
init?(stringValue: String)
var intValue: Int? { get }
init?(intValue: Int)
}
init(name: String, age: Int, address: Address)
init(from decoder: Decoder) throws
func encode(to encoder: Encoder) throws
}
编码逻辑补充
对于 Encodable,编译器为 Student
自动生成的
encode(to:)
方法,如下所示。内部使用 container
缓存编码结果,并调用 __JSONKeyedEncodingContainer
的
encode(_:forKey:)
方法,而 encode(_:forKey:)
方法则调用了 __JSONEncoder
中的 encode(_:)
方法。encode(_:)
方法又会执行 box_(_:)
方法,并生成新的 container。最终形成了一个递归调用。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// Student.encode(to:)
sil hidden @$s7StudentAAV6encode2toys7Encoder_p_tKF : $ (method) ( Encoder, Student) -> Error {
// %0 "encoder" // users: %6, %2
// %1 "self" // users: %35, %24, %11, %3
bb0(%0 : $*Encoder, %1 : $Student):
debug_value_addr %0 : $*Encoder, let, name "encoder", argno 1 // id: %2
debug_value %1 : $Student, let, name "self", argno 2 // id: %3
debug_value undef : $Error, var, name "$error", argno 3 // id: %4
// 初始化 KeyedEncodingContainer 内存布局
%5 = alloc_stack $KeyedEncodingContainer<Student.CodingKeys>, var, name "container" // users: %52, %51, %74, %73, %66, %65, %60, %59, %10, %17, %29, %43
// 获取遵循 Encoder 协议的实体,即 __JSONEncoder
%6 = open_existential_addr immutable_access %0 : $*Encoder to $* ("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder // users: %10, %10, %9
%7 = metatype $ Student.CodingKeys.Type
%8 = metatype $ Student.CodingKeys.Type // user: %10
// 获取 __JSONEncoder 的 container 方法
%9 = witness_method $ ("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder, #Encoder.container : <Self where Self : Encoder><Key where Key : CodingKey> (Self) -> (Key.Type) -> KeyedEncodingContainer<Key>, %6 : $* ("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder : $ (witness_method: Encoder) <τ_0_0 where τ_0_0 : Encoder><τ_1_0 where τ_1_0 : CodingKey> ( τ_1_0.Type, τ_0_0) -> KeyedEncodingContainer<τ_1_0> // type-defs: %6; user: %10
// 执行 __JSONEncoder 的 container 方法
%10 = apply %9< ("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder, Student.CodingKeys>(%5, %8, %6) : $ (witness_method: Encoder) <τ_0_0 where τ_0_0 : Encoder><τ_1_0 where τ_1_0 : CodingKey> ( τ_1_0.Type, τ_0_0) -> KeyedEncodingContainer<τ_1_0> // type-defs: %6
%11 = struct_extract %1 : $Student, #Student.name // users: %58, %23, %19, %12
retain_value %11 : $String // id: %12
%13 = metatype $ Student.CodingKeys.Type
// 分配一个 CodingKeys 内存,将 name 的枚举值写入
%14 = enum $Student.CodingKeys, #Student.CodingKeys.name!enumelt // user: %16
%15 = alloc_stack $Student.CodingKeys // users: %16, %22, %19, %57
store %14 to %15 : $*Student.CodingKeys // id: %16
%17 = begin_access [modify] [static] %5 : $*KeyedEncodingContainer<Student.CodingKeys> // users: %21, %19, %56
// 获取 KeyedEncodingContainer 的 encode(_:forKey:) 方法,即 __JSONKeyedEncodingContainer 的 encode(_:forKey:)
// function_ref KeyedEncodingContainer.encode(_:forKey:)
%18 = function_ref @$ss22KeyedEncodingContainerV6encode_6forKeyySS_xtKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@guaranteed String, @in_guaranteed τ_0_0, @inout KeyedEncodingContainer<τ_0_0>) -> @error Error // user: %19
// 执行 KeyedEncodingContainer 的 encode(_:forKey:) 方法,即 __JSONKeyedEncodingContainer 的 encode(_:forKey:)
try_apply %18<Student.CodingKeys>(%11, %15, %17) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@guaranteed String, @in_guaranteed τ_0_0, @inout KeyedEncodingContainer<τ_0_0>) -> @error Error, normal bb1, error bb4 // id: %19
...
解码逻辑补充
对于 Decodable,编译器为 Student
自动生成的
init(from:)
方法,如下所示。内部使用 container
缓存编码结果,并调用 __JSONKeyedDecodingContainer
的
decode(_:forKey:)
方法,而 decode(_:forKey:)
方法则调用了 __JSONDecoder
中的 decode(_:)
方法。decode(_:)
方法又会执行 unbox_(_:)
方法,并生成新的 container。最终形成了一个递归调用。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// Student.init(from:)
sil hidden @$s7StudentAAV4fromABs7Decoder_p_tKcfC : $ (method) ( Decoder, Student.Type) -> ( Student, Error) {
// %0 "decoder" // users: %97, %70, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $ Student.Type):
%2 = alloc_stack $Builtin.Int3 // users: %105, %98, %59, %27, %5, %115, %73
%3 = alloc_stack [dynamic_lifetime] $Student, var, name "self" // users: %56, %40, %24, %71, %101, %110, %114, %72
%4 = integer_literal $Builtin.Int3, 0 // user: %5
store %4 to %2 : $*Builtin.Int3 // id: %5
debug_value_addr %0 : $*Decoder, let, name "decoder", argno 1 // id: %6
debug_value undef : $Error, var, name "$error", argno 2 // id: %7
// 初始化 KeyedDecodingContainer 内存布局
%8 = alloc_stack $KeyedDecodingContainer<Student.CodingKeys>, let, name "container" // users: %65, %64, %52, %94, %93, %37, %87, %86, %21, %81, %80, %13, %76
// 获取遵循 Decoder 协议的实体,即 __JSONDecoder
%9 = open_existential_addr immutable_access %0 : $*Decoder to $* ("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder // users: %13, %13, %12
%10 = metatype $ Student.CodingKeys.Type
%11 = metatype $ Student.CodingKeys.Type // user: %13
// 获取 __JSONDecoder 的 container 方法的地址,该方法的类型是 <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>
%12 = witness_method $ ("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $* ("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder : $ (witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> ( τ_1_0.Type, τ_0_0) -> ( KeyedDecodingContainer<τ_1_0>, Error) // type-defs: %9; user: %13
// 执行 __JSONDecoder 的 container 方法
try_apply %12< ("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder, Student.CodingKeys>(%8, %11, %9) : $ (witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> ( τ_1_0.Type, τ_0_0) -> ( KeyedDecodingContainer<τ_1_0>, Error), normal bb1, error bb5 // type-defs: %9; id: %13
bb1(%14 : $()): // Preds: bb0
%15 = metatype $ String.Type // user: %21
%16 = metatype $ Student.CodingKeys.Type
// 分配一个 CodinggKeys 内存,将 name 的枚举值写入
%17 = enum $Student.CodingKeys, #Student.CodingKeys.name!enumelt // user: %19
%18 = alloc_stack $Student.CodingKeys // users: %19, %23, %21, %79
store %17 to %18 : $*Student.CodingKeys // id: %19
// 获取 KeyedDecodingContainer 中的 decode(_:forKey:) 方法,即 __JSONKeyedDecodingContainer 的 decode(_:forKey:)
// 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
// 执行 KeyedDecodingContainer 中的 decode(_:forKey:) 方法,即 __JSONKeyedDecodingContainer 的 decode(_:forKey:)
try_apply %20<Student.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 bb6 // id: %21
...
总结
至此为止,Codable
中编码和解码的逻辑就形成了闭环。总体而言,编码和解码的核心逻辑主要在
__JSONEncoder
和 __JSONDecoder
中,Container
则用于缓存编码和解码的结果,对于多层级嵌套的数据或模型,在编解码过程中会针对每个层级生成对应的
container。整个过程是递归完成的,在过程中会判断 container
所处的层级,从而决定是否结束递归。
另外很重要的一点是,Codable 之所以如此简单易用,是因为编译器为 Codable 协议自动生成了大量模板代码,从而简化了开发者的工作量,使得 Codable 变得简单易用。
最后,我们可以看到 Codable 的实现中采用了大量面向协议编程的思想,并解决了泛型协议的类型擦除问题。这些思想在我们实际开发中也是有极大的借鉴意义的。
参考
- Codable.swift
- JSONEncoder.swift
- 《Flight School - Guide to Swift Codable》
- Swift 5 支持自定义编码的三种容器