Encodable
表示一种可以被编码器
进行编码数据结构。比如JSONEncoder
可以将其编码为JSON
格式,PropertyListEncoder
可以将其编码为.plist
格式,而Alamofire
中的URLEncodedFormEncoder
可以将其编码为application/x-www-form-urlencoded
格式。
将支持Encodable
的数据进行编码,系统做了很好的抽象,这才有了诸多类型的编码器
。今天一起来探索下其中的奥秘。
下面的内容我将使用
JSONEncoder
和URLEncodedFormEncoder
作为参考,其他类型的编码器请自行研究。
编码器只是起点
不管是JSONEncoder
还是URLEncodedFormEncoder
,它们只是整个编码过程的起点。在需要编码时,只需调用各自的encode
方法。除了提供最基本编码入口之外,它们还提供了丰富的配置项,以便个性化输出。比如JSONEncoder
,支持如下配置:
1 |
|
再比如URLEncodedFormEncoder
:
1 |
|
可以看到后者的配置项明显的多于前者,这也是为了输出不同格式的结果。
一线的高光者们
在调用Encoder
们的encode
方法后,我们的Encodable
的encode(to encoder: Encoder)
方法将被调用。这里是进行具体编码的主要战场。
不熟悉手动编码的同学,可以参考这篇文章。这里包含了大多数使用场景,很适合练手。
Encoder协议
我们先看下Encoder
的定义:
1 |
|
通过上面的定义以及我们使用Encoder
经验,不难看出Encoder
在实际的编码过程中充当了管理者
的身份。它主要负责记录当前encode
的状态,比如当前解析的路径,提供存储值的容器。
作为管理者,当然不能什么事都亲自处理。而容器
正是其得力助手,可以说容器
才是实际的搬砖者。
EncodingContainers
EncodingContainer
可以理解为数据的存储器,任何满足Encodable
的数据都可以存在其中,它主要有3种:
KeyedEncodingContainer
:负责Key-Value
结构。UnkeyedEncodingContainer
:负责数组
结构。SingleValueEncodingContainer
:负责单一值结构,如Int
、String
、AnyEncodable
一个EncodingContainer
的内容大致分为几类:
- 上下文信息
- 具体类型的编码支持方法
- 嵌套容器支持方法
superDecoder
下面以KeyedEncodingContainer
为例说明其中包含的主要组成部分:
1 |
|
其他类型的容器也大同小异,都是在为各种数据类型和各种编码情况作支持。
自定义编码器
URLEncodedFormEncoder
及相关类实现了自定义的编码器,接下来我们一起来研究下具体细节。
和JSONEncoder
一样,URLEncodedFormEncoder
并不遵循Encoder
协议。它们只提供配置及接口,不是实际的工作者。
而_URLEncodedFormEncoder
实现了Encoder
。它提供了一个Encoder
所必须的必要条件:
1 |
|
三种容器
这里提供的三种容器,均定义在_URLEncodedFormEncoder
扩展中,然后以扩展的形式遵循对应的容器协议:
每种容器都有一些统一的配置:
1 |
|
由于每种容器由于处理的结构不一样,它们各自还有一些特有功能支持。
KeyedContainer
KeyedContainer
处理的是Key-Value
结构,这种结构支持嵌套,所以在解析路径
上面也需要支持:
1 |
|
在其他实现上,KeyedContainer
实现了一个泛型编码方法:
1 |
|
对于Key-Value
来说,任意Key
对应Encodable
类型的Value
都是单一的值。所以这里直接将进一步的解析交给SingleValueContainer
。
其他关于各种容器的切换就是返回对应的类型,这里就不再细说。
UnkeyedContainer
和KeyedContainer
一样,UnkeyedContainer
也是支持嵌套的,但是在解析路径
的实现细节上有所不同:它没有键名,所以使用了索引来生成嵌套的路径。
1 |
|
AnyCodingKey
是为了支持索引
到CodingKey
的转变而添加的类型。
在其他实现上,对于各种类型的编码支持也落脚到一个泛型方法:
1 |
|
UnkeyedContainer
也是支持嵌套的,所以也可以按照KeyedContainer
的逻辑来实现。
需要注意的是:数组结构是按照索引,从前到后依次解析的,所以在每解析一个元素后,索引就会向后移动一个。这也就引申出UnkeyedContainer
的另外一个配置count
,它记录着当前解析元素的位置。所以,这里的每一次容器转换,都会对count
进行+1
。
SingleValueContainer
前面说过SingleValueContainer
代表了一个Encodable
的单值结构。所以它只能被编码一次。这里通过canEncodeNewValue
来记录是否可以编码,并提供一个检查方法,在异常时抛出错误:
1 |
|
由于SingleValueContainer
是编码结构中的叶节点,它提供了全套的基本类型编码方法支持,以及nil
和Encodable
。基本类型的编码,都会落脚到这里(记为worker,后面还会用到):
1 |
|
在这里改变了上下文中的内容,将编码后的值存储在其中。而对于Encodable
类型的支持,会先判断是否为Date
/Data
/Decimal
,若满足条件,会通过指定的转换策略转换为String
,最后送入worker
:
1 |
|
在其他类型上,会通过attemptToEncode
方法再次进入_URLEncodedFormEncoder
的工作流程中。
而EncodingError.Context
是整个过程中的记录者,存储着Encodable
的另一种表现形式。完成从Encodable
到EncodingError.Context
的转换后,Encoder
的工作就可以告一段落了。下面一起来瞅瞅这个Context
。
接力棒Context
URLEncodedFormContext
的设计非常简单。只有一个component
成员:
1 |
|
而URLEncodedFormComponent
是真正的具体值。它是一个枚举,各种case
正代表了一个Encodable
的各种情况:
1 |
|
假如有如下Encodable
:
1 |
|
那么它对应到URLEncodedFormComponent
的情况如下:
1 |
|
component
在整个解析过程中是动态变化的,这主要通过下面的方法:
1 |
|
最后的站点
要得到application/x-www-form-urlencoded
格式的字符串,我们还差最后一步-序列化!这一步是由URLEncodedFormSerializer
负责。
URLEncodedFormSerializer
主要有两部分组成:配置和支持方法。
在前面讲到URLEncodedFormEncoder
的配置时,一共列出了8
个。其中3
(Date
、Date
、bool
类型的解析策略)个在容器里使用了;剩下的5
个会在这里登场:
1 |
|
编码支持方法,主要完成URLEncodedFormComponent
到String
的任务:
1 |
|
总结
今天我们主要了解了Encodable
相关组成,并以URLEncodedFormEncoder
为例子分析了如何实现一个自定义的Encodable
。它主要有两大步骤:
- 实现
Encoder
协议,提供3中容器支持,完成从Encodable
到URLEncodedFormComponent
的转换 - 使用序列化器将
URLEncodedFormComponent
转换为字符串
希望对大家有所帮助,再会!