这是Swift数据解析方案的系列文章:
Swift数据解析(第四篇) - SmartCodable 上
Swift数据解析(第四篇) - SmartCodable 下
一. 如何找到源码
Swift的源码是公开在github上的,可以通过 Swift源码 该链接查看。
跟Codable有关的源码在这两个文件中 Codable.swift 和 JSONDecoder.swift。
二. 如何阅读源码
我对codable相关的源码读了三遍。这是因为:
第一遍
直接读这两个文件代码,代码量太多,几乎没收获。
第二遍
尝试对这两个文件进行拆分,按照类/结构体直接对相互关系,分为三大类。这才理解了各个类之间的作用。
你可以下载 Codable源码拆分
第三遍
虽然读过两遍之后,大致理解了各个类/结构体的作用。但是看到更底层的逻辑,比如带键解码容器的实现, 使用了4层设计KeyedDecodingContainerProtocol, KeyedDecodingContainer, _KeyedDecodingContainerBox, _KeyedDecodingContainerBase。 理解起来非常吃力。
我更希望知道这些 类/协议/结构体 是如何工作的?各自承担了什么职责?这么设计的目的是什么?
于是我从最开始的调用代码开始,跟随着代码执行的顺序,一步步的理解它。
三. decode过程
我们以这个简单的示例为开始:
swift
do {
let decoder = JSONDecoder()
let feed = try decoder.decode(Person.self, from: jsonData)
print(feed)
} catch let error {
print(error)
}
JSONDecoder
swift
open class JSONDecoder {
public init() {}
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} 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: T.self) else {
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
}
JSONDecoder是对外的调用类,内部的实现靠 _JSONDecoder 完成。
- 入参的泛型
T
必须遵循Decodable
协议。 - 使用
JSONSerialization
将data
数据序列化。 - 调用内部类
_JSONDecoder
,依赖数据字典和编码策略生成decoder
对象。 decoder
对象调用unbox
方法,进行解码并返回解码值。
_JSONDecoder
_JSONDecoder
是用来解码操作的内部类,它遵循了Decoder
协议。 具体代码
swift
class _JSONDecoder : Decoder {
fileprivate var storage: _JSONDecodingStorage
fileprivate let options: JSONDecoder._Options
fileprivate(set) public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
......
}
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
......
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
......
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return self
}
}
init方法
swift
init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
self.storage = _JSONDecodingStorage()
self.storage.push(container: container)
self.codingPath = codingPath
self.options = options
}
它的主要工作是创建内部类 _JSONDecodingStorage
, 并存储当前的解码数据。分别初始化 options
和 codingPath
。
_JSONDecodingStorage
swift
fileprivate struct _JSONDecodingStorage {
/// The container stack.
/// Elements may be any one of the JSON types (NSNull, NSNumber, String, Array, [String : Any]).
private(set) fileprivate var containers: [Any] = []
fileprivate init() {}
// MARK: - Modifying the Stack
fileprivate var count: Int {
return self.containers.count
}
fileprivate var topContainer: Any {
precondition(self.containers.count > 0, "Empty container stack.")
return self.containers.last!
}
fileprivate mutating func push(container: Any) {
self.containers.append(container)
}
fileprivate mutating func popContainer() {
precondition(self.containers.count > 0, "Empty container stack.")
self.containers.removeLast()
}
}
主要是用来管理要解码的数据。
- 解码前:通过push方法存入containers里面。
- 解码时:通过topContainer获取需要解码的数据。
- 解码后:调用popContainer方法移除记录的数据。
unbox
unbox
方法用于解码操作,匹配对应的类型然后执行条件分支。
swift
extension _JSONDecoder {
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
// 判断type的类型,针对不同的类型,调用不同的方法。
let decoded: T
if T.self == Date.self || T.self == NSDate.self {
guard let date = try self.unbox(value, as: Date.self) else { return nil }
decoded = date as! T
} else if T.self == Data.self || T.self == NSData.self {
guard let data = try self.unbox(value, as: Data.self) else { return nil }
decoded = data as! T
} else if T.self == URL.self || T.self == 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."))
}
decoded = (url as! T)
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
decoded = decimal as! T
} else {
self.storage.push(container: value)
decoded = try T(from: self)
self.storage.popContainer()
}
return decoded
}
}
/// 如果类型是String
fileprivate func unbox(_ value: Any, as type: String.Type) throws -> String? {
guard !(value is NSNull) else { return nil }
guard let string = value as? String else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
return string
}
/// 如果类型是Bool
fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { }
/// 还有其他几种类型,不再一一列举
在该方法中,通过判断要解码的类型type,决定使用哪个解码方法。我们是使用的model解码,会进入else逻辑中
php
self.storage.push(container: value)
decoded = try T(from: self)
self.storage.popContainer()
container
swift
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
// 最后一个推入的json数据 是否为 null
guard !(self.storage.topContainer is NSNull) else {
throw DecodingError.valueNotFound(
KeyedDecodingContainer<Key>.self,
DecodingError.Context(codingPath:self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
// 最后一个json数据是否为字典类型
guard let topContainer = self.storage.topContainer as? [String : Any] else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer)
}
let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
return KeyedDecodingContainer(container)
}
四. 解码异常情况
JSONDecoder中的decode方法
swift
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} 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: T.self) else {
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
数据序列化失败: 数据是非法的json。
_JSONDecoder.unbox 失败:给定的数据不包含顶级值。
_JSONKeyedDecodingContainer
swift
public func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key (key) ("(key.stringValue)")."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: T.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected (type) value but found null instead."))
}
return value
}
- 键缺失的情况的异常
- null值的异常。
unbox
swift
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
guard !(value is NSNull) else { return nil }
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let int = number.intValue
guard NSNumber(value: int) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <(number)> does not fit in (type)."))
}
return int
}
- 类型不匹配时的异常
- 数据损坏时候的异常。
- 我们将针对这些异常提供兼容代码,避免一个字段解析错误导致整个解析失败。
五. 源码阅读过程中的疑惑
疑惑1: 系统的提供的数据类型(Int / String / Bool等)为什么可以直接进行解码?
数据类型默认实现了Codable协议。
swift
extension Bool : Codable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
self = try decoder.singleValueContainer().decode(Bool.self)
}
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self)
}
}
疑惑2: decodeIfPresent 和 decode 两个方法什么时候会调用?
在Swift的Codable中,使用decodeIfPresent
和decode
的区别在于处理的属性是否可选值(Optional)。
decodeIfPresent
方法用于解码可选值,如果解码成功,则返回解码后的值,如果解码失败,则返回nil
。decode
方法用于解码非可选值,如果解码成功,则返回解码后的值,如果解码失败,则抛出一个错误。
因此,当你需要解码一个可能为nil
的值时,你可以使用decodeIfPresent
方法。而当你需要解码一个非可选值时,你应该使用decode
方法。
在Codable的源码中,调用decodeIfPresent
还是decode
方法是由编译器在编译时根据上下文来决定的。编译器会根据解码值的类型和键的存在与否来选择调用适当的方法。
疑惑3: 使用decodeIfPresent解码,遇到类型不匹配的情况还是会抛出异常?
swift
public extension KeyedDecodingContainerProtocol {
public func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? {
guard try self.contains(key) && !self.decodeNil(forKey: key) else { return nil }
return try self.decode(Bool.self, forKey: key)
}
}
在decodeIfPresent的实现中,进行了这样两次的判断:
- 是否包含这个key:contains(key)
- 是否为nil:decodeNil(forKey: key)
没有针对类型不匹做处理。
疑惑4: 枚举值是如何编码和解码的?
swift
public extension RawRepresentable where RawValue == Int, Self : Encodable {
@_inlineable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.rawValue)
}
}
public extension RawRepresentable where RawValue == Int, Self : Decodable {
@_inlineable // FIXME(sil-serialize-all)
public init(from decoder: Decoder) throws {
let decoded = try decoder.singleValueContainer().decode(RawValue.self)
guard let value = Self(rawValue: decoded) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Cannot initialize \(Self.self) from invalid \(RawValue.self) value \(decoded)"))
}
self = value
}
}
先按照枚举项的类型decode出来值let decoded = try decoder.singleValueContainer().decode(RawValue.self)
, 然后通过rawValue生成对应的枚举项。 这个过程会抛出两种异常情况: decode失败 和 转换枚举项失败。
疑惑5: URL是如何解码的?
swift
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
......
} else if T.self == URL.self || T.self == 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."))
}
decoded = (url as! T)
}
......
return decoded
}
先unbox(value, as: String.self)
出来String类型的值, 如果转URL失败抛出异常。
疑问6: 遵守Codable协议没有实现对应的协议方法,为什么不报错?
实现一个简单编解码只需要遵守Codable就行了,Codable书写起来很方便。这在个美好的体验中,会有这样的疑惑: 为什么会这么方便? 协议方法是谁来实现的,怎么实现的?
swift
public protocol Encodable {
func encode(to encoder: Encoder) throws
}
public protocol Decodable {
init(from decoder: Decoder) throws
}
public typealias Codable = Decodable & Encodable
跟OC中的Clang一样,Swift代码在编译过程中也会转换成中间码,即:SIL。
Swift编程语言是在LLVM上构建,并且使用LLVM IR和LLVM的后端去生成代码。但是Swift编译器还包含新的高级别的中间语言,称为
SIL
。SIL
会对Swift进行较高级别的语义分析和优化。
可以通过 swiftc SomeOne.swift -emit-sil
将该代码
ini
## SomeOne.swift 中的代码
struct SomeOne: Codable {
var name: String = ""
var age: Int = 0
}
转换成SIL中间码:
swift
sil_stage canonical
import Builtin
import Swift
import SwiftShims
struct SomeOne : Decodable & Encodable {
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue var age: Int { get set }
enum CodingKeys : CodingKey {
case name
case age
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: SomeOne.CodingKeys, _ b: SomeOne.CodingKeys) -> Bool
func hash(into hasher: inout Hasher)
init?(stringValue: String)
init?(intValue: Int)
var hashValue: Int { get }
var intValue: Int? { get }
var stringValue: String { get }
}
func encode(to encoder: Encoder) throws
init()
init(from decoder: Decoder) throws
init(name: String = "", age: Int = 0)
}
Xcode 在编译Swift代码的时,会对遵守codable协议的代码自动实现 func encode(to encoder: Encoder) throws
和 init(from decoder: Decoder) throws
。
这种编译成面的隐式处理,简化了代码的同时,也提高了阅读成本。
疑问7: 为什么使用CodingKeys自定义模型中的key?
swift
enum CodingKeys : CodingKey {
case name
case age
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: SomeOne.CodingKeys, _ b: SomeOne.CodingKeys) -> Bool
func hash(into hasher: inout Hasher)
init?(stringValue: String)
init?(intValue: Int)
var hashValue: Int { get }
var intValue: Int? { get }
var stringValue: String { get }
}
如上所示: SIL中间码会对遵守Codable的代码默认生成CodingKeys,并且会根据CodingKeys处理编解码的映射关系。 同样也可以解释为什么自定义的CodingKeys需要声明在结构体内部。