源码解读——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
46
struct 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"))

上述代码描述了一种关于 Codable 的常见用法,我们声明了一个 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
11
protocol Encodable {
// 把值编码到 encoder 中
func encode(to encoder: Encoder) throws
}

protocol Decodable {
// 从 decoder 中把值解码出来
init(from decoder: Decoder) throws
}

typealias Codable = Decodable & Encodable

在使用示例中,我们可以看到,模型只要遵循了 Codable 协议就可以进行数据和模型的相互转换。不过,我们可能会产生一个疑问:StudentAddress 类遵循了 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
23
protocol 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,上述例子中 StudentAddress 都是基于 KeyedContainer<Key> 完成的。

下图所示分别是 KeyedEncodingContainer<Key>KeyedDecodingContainer<Key> 相关的类图。在《Swift 类型擦除》文中,我们介绍了下面这种结构,它的主要作用是 对泛型协议进行类型擦除,从而能够实现泛型协议的存储

其中,KeyedEncodingContainerProtocolKeyedDecodingContainerProtocol 是泛型协议。协议的真正实现者是 __JSONKeyedEncodingContainer__JSONKeyedDecodingContainer,两者是泛型结构体。它们内部则分别调用了 __JSONEncoder__JSONDecoder 内部的相关方法,从而完成编码和解码的核心逻辑。__JSONEncoder__JSONDecoder 也正是 Encoder 协议和 Decoder 协议的实现者。

在图中,其他的类,包括:KeyedEncodingContainer<Key>_KeyedEncodingContainerBase_KeyedEncodingContainerBoxKeyedDecodingContainer<Key>_KeyedDecodingContainerBase_KeyedDecodingContainerBox,它们内部对方法调用进行了层层转发,目的是辅助解决类型擦除问题。

UnkeyedContainer

UnkeyedContainer 主要用于不带键数据的编解码,比如,使用 UnkeyedEncodingContainer<Key> 能够编码得到类似 ["baochuquan", 18] 这样的结果。既然是 Unkeyed,那就是结果中只有 value,没有 key。总体来说,UnkeyedContainer 使用较少。

下图所示分别是 UnkeyedEncodingContainerUnkeyedDecodingContainer 相关的类图。由于它们并不需要支持泛型,所以无需一堆辅助类来实现类型擦除。

其中,UnkeyedEncodingContainerUnkeyedDecodingContainer 的真正实现者分别是 __JSONUnkeyedEncodingContainer__JSONUnkeyedDecodingContainer。它们内部则分别调用了 __JSONEncoder__JSONDecoder 内部的相关方法,从而完成编码和解码的核心逻辑。

SingleValueContainer

SingleValueContainer 主要用于单个值数据的编解码,比如,使用 SingleEncodingContainer 能够编码得到类似 "baochuquan" 这样的结果。它只是一个值,并不是字典或数组。总体来说,用的也比较少。

下图所示分别是 SingleValueEncodingContainerSingleValueDecodingContainer 的相关类图。同样,它们也不支持泛型,无需辅助类来实现类型擦除。

这里,SingleValueEncodingContainerSingleValueDecodingContainer 的真正实现者直接就是 __JSONEncoder__JSONDecoder

小结

从上述三种 container 的类图中我们可以发现,__JSONEncoder__JSONDecoder 才是真正实现编码和解码的关键

核心逻辑

接下来,我们从上述两个例子开始,研究一下 Codable 编码和解码的核心逻辑。

编码逻辑

如下所示,在编码过程中,首先会调用初始化一个 JSONEncoder 对象,然后调用其 encode(_:) 方法。

1
2
3
4
5
6
7
let encoder = JSONEncoder()
guard
let data = try? encoder.encode(student),
let encodedString = String(data: data, encoding: .utf8)
else {
fatalError()
}

JSONEncoderencode(_:) 方法内部逻辑如下所示,核心部分就是初始化一个 __JSONEncoder 对象,调用其 box_(_:) 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func 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))
}
}

__JSONEncoderbox_(_: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
40
func 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
7
let decoder = JSONDecoder()
guard
let jsonData = jsonString.data(using: .utf8),
let model = try? decoder.decode(Student.self, from: jsonData)
else {
fatalError()
}

JSONDecoderdecode(_:from:) 方法内部逻辑如下所示,核心部分就是初始化一个 __JSONDecoder 对象,调用其 unbox(_:as:) 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func 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
}

__JSONDecoderunbox(_: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
25
func 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

我们首先能看到编译器生成了 StudentAddress 相关的代码,如下所示。对于未指定 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
39
struct Address : Decodable & Encodable {
@_hasStorage var province: String { get set }
@_hasStorage var city: String { get set }
init(province: String, city: String)
enum CodingKeys : CodingKey {
case province
case city
@_implements(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 {
@_hasStorage var name: String { get set }
@_hasStorage var age: Int { get set }
@_hasStorage 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 缓存编码结果,并调用 __JSONKeyedEncodingContainerencode(_: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 : $@convention(method) (@in_guaranteed Encoder, @guaranteed Student) -> @error 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 $*@opened("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder // users: %10, %10, %9
%7 = metatype $@thin Student.CodingKeys.Type
%8 = metatype $@thick Student.CodingKeys.Type // user: %10

// 获取 __JSONEncoder 的 container 方法
%9 = witness_method $@opened("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder, #Encoder.container : <Self where Self : Encoder><Key where Key : CodingKey> (Self) -> (Key.Type) -> KeyedEncodingContainer<Key>, %6 : $*@opened("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder : $@convention(witness_method: Encoder) <τ_0_0 where τ_0_0 : Encoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out KeyedEncodingContainer<τ_1_0> // type-defs: %6; user: %10

// 执行 __JSONEncoder 的 container 方法
%10 = apply %9<@opened("563EEE00-301D-11EC-B195-AC87A33022EC") Encoder, Student.CodingKeys>(%5, %8, %6) : $@convention(witness_method: Encoder) <τ_0_0 where τ_0_0 : Encoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out 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 $@thin 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

// 获取 KeyedEncodingContainerencode(_:forKey:) 方法,即 __JSONKeyedEncodingContainerencode(_: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

// 执行 KeyedEncodingContainerencode(_:forKey:) 方法,即 __JSONKeyedEncodingContainerencode(_: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 缓存编码结果,并调用 __JSONKeyedDecodingContainerdecode(_: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 : $@convention(method) (@in Decoder, @thin Student.Type) -> (@owned Student, @error Error) {
// %0 "decoder" // users: %97, %70, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin 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 $*@opened("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder // users: %13, %13, %12
%10 = metatype $@thin Student.CodingKeys.Type
%11 = metatype $@thick Student.CodingKeys.Type // user: %13

// 获取 __JSONDecoder 的 container 方法的地址,该方法的类型是 <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>
%12 = witness_method $@opened("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("563E18FE-301D-11EC-B195-AC87A33022EC") 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

// 执行 __JSONDecoder 的 container 方法
try_apply %12<@opened("563E18FE-301D-11EC-B195-AC87A33022EC") Decoder, Student.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 bb5 // type-defs: %9; id: %13

bb1(%14 : $()): // Preds: bb0
%15 = metatype $@thin String.Type // user: %21
%16 = metatype $@thin 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:) 方法,即 __JSONKeyedDecodingContainerdecode(_: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:) 方法,即 __JSONKeyedDecodingContainerdecode(_: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 的实现中采用了大量面向协议编程的思想,并解决了泛型协议的类型擦除问题。这些思想在我们实际开发中也是有极大的借鉴意义的。

参考

  1. Codable.swift
  2. JSONEncoder.swift
  3. 《Flight School - Guide to Swift Codable》
  4. Swift 5 支持自定义编码的三种容器