带有Encoder和Encodable的JSON

Swift 4带来了一种更加原生的方式来编码和解码实例,并内置了对每个人最喜欢的基于文本的格式的支持:JSON!

我们不使用所有的编码和解码源代码,而是采用一种不同的方法,并逐步通过一个简单的示例:单个Int实例如何通过JSONEncoder变成JSON数据?

从那里,我们应该能够更进一步,了解其他原始类型,数组,字典等是如何编码的。

封存

NSCoding作为Cocoa的一部分已经存储和检索数据很长时间了。 令人振奋的消息是,由于NSKeyedArchiver已有15年的历史了,Apple终于宣布弃用NSArchiver 。 😜

一个好主意是,如果可以对诸如字符串和数字之类的单个实例进行编码解码 ,则可以存档取消存档整个对象图。

编码所有事物

在Swift标准库中,除了编码 之外,还有一些可编码的东西。

  • 可编码是一种协议。 符合类型可以将自身编码为其他表示形式。
  • 编码器也是一种协议。 编码器负责将可Encodable内容转换为其他格式,例如JSON或XML。

Encodable类似于NSCoding但作为Swift协议,您的Swift结构和枚举也可以加入聚会。 类似地,尽管Encoder还是协议而不是抽象类,但它与NSCoder对应。

一个简单的整数

您不能使用JSONEncoder编码裸标量,而是需要顶级数组或字典。 为简单起见,让我们从编码包含单个整数[42]的数组开始。

 let encoder = JSONEncoder() 
let jsonData = try! encoder.encode([42])

首先,我们实例化JSONEncoder ,然后使用数组在其上调用encode() 。 那里发生了什么事?

 // JSONEncoder.swift 
open func encode(_ value: T) throws -> Data {
let encoder = _JSONEncoder(options: self.options)

encode()方法采用一些Encodable值,并返回原始JSON Data

实际的编码工作在私有类_JSONEncoder 。 此方法将JSONEncoder保留为具有友好公共接口的类型,并将_JSONEncoder为实现Encoder协议的fileprivate (每个人的最爱!)类。

  // continued from above 
try value.encode(to: encoder)

注意相反的情况:在原始呼叫站点,我们要求编码器对值进行编码; 在此,编码器要求该将其自身编码专用编码器。

可编码

让我们退后一步,在这里查看协议的相关部分。

首先是可Encodable -请记住,这是用于整数和数组等可编码的协议。

 public protocol Encodable { 
func encode(to encoder: Encoder) throws
}

我们将对包装数组[42]并只考虑整数值42以使事情变得简单。 Int符合Encodable ,我们可以看一下它的encode(to:)方法的作用:

 extension Int : Codable { 
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self)
}
}

我们要求编码器提供一个容器,然后要求该容器编码self ,即整数值。

编码器

我们的下一个侧边栏是讨论Encoder -这是_JSONEncoder这样的类的协议,这些协议将可编码的值转换为某种一致的格式非常繁重。

 public protocol Encoder { 
// [...]
func container(keyedBy type: Key.Type) -> KeyedEncodingContainer
func unkeyedContainer() -> UnkeyedEncodingContainer
func singleValueContainer() -> SingleValueEncodingContainer
}

归功于上述三种访问器方法,编码器可以处理三种值:

  1. 键控容器(词典)
  2. 未加密的容器(数组)
  3. 单值容器(用于标量值)

返回代码以对Int进行编码:

 // extension Int : Codable 
var container = encoder.singleValueContainer()
try container.encode(self)

首先,从JSON编码器获取一个单值容器,其代码如下:

 // _JSONEncoder 
func singleValueContainer() -> SingleValueEncodingContainer {
return self
}

好吧,这很简单: _JSONEncoder本身符合SingleValueEncodingContainer并返回自身。 让我们快速浏览一下该协议:

 public protocol SingleValueEncodingContainer { 
// [...]
mutating func encode(_ value: Int) throws
}

对于各种简单类型,例如BoolString等,还有许多其他的encode方法。但是我们感兴趣的是Int

 // extension _JSONEncoder : SingleValueEncodingContainer 
func encode(_ value: Int) throws {
assertCanEncodeNewValue()
self.storage.push(container: box(value))
}

这是关键时刻! 有某种存储方式,我们正在将盒装价值推到上面。

但是存储空间是多少? 盒子里有什么? 🤔

存储

要了解存储,让我们跳到最后。 我们在这里陷入困境,但是您还记得我们是如何开始的吗? 这是JSONEncoder中的encode()方法,我们在数组中传递了一个整数。 该方法的最后一行如下所示:

 // JSONEncoder.swift 
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)

因此,这一切的全部要点是拥有一个顶级容器(数组或字典),该容器将传递给JSONSerialization ,以前称为NSJSONSerialization

该存储是另一个文件fileprivate结构,该结构保留一堆容器。 当心,Objective-C将在这里开始展示自己:

 fileprivate struct _JSONEncodingStorage { 
/// The container stack.
/// Elements may be any one of the JSON types
/// (NSNull, NSNumber, NSString, NSArray, NSDictionary).
private(set) var containers: [NSObject] = []
}

这就解释了盒子。 我们的数组[42]将变成一个NSMutableArray ,上面的box()调用会将整数变成一个NSNumber ,该NSNumber被添加到该数组中:

 // extension _JSONEncoder 
fileprivate func box(_ value: Int) -> NSObject {
return NSNumber(value: value)
}

事实证明,如果您做得足够深入,那就是Objective-C。

总结一下: JSONEncoder和朋友将我们的Swift值转换为它们的Foundation对象等效JSONEncoder ,然后通过JSONEncoder将这些对象转换为JSON。

结束括号

这是要求JSONEncoder用单个整数[42]编码数组的最后一组步骤:

  1. 数组,将自己编码 _JSONEncoder
  2. 数组设置非密钥容器( NSMutableArray存储)
  3. 数组遍历所有元素并对它们进行编码

一种。 整数,将自己编码为_JSONEncoder

b。 整数设置单值容器

C。 整数要求容器自行编码

  • 容器将整数放入NSNumber ,添加到数组中

7.使用JSONSerialization对顶级NSMutableArray进行编码

8.利润! 💰

您可以在标准库源的JSONEncoder.swift和Codable.swift中找到所有相关代码。

现在相反,解码呢? 以及如何编写自定义编码器和解码器以用于除JSON之外的其他格式(例如协议缓冲区)? 请继续关注更多信息,或者为什么不深入研究代码并查看发现的内容呢?

}