这是Swift数据解析计划的系列文章:
Swift数据解析(第一篇) – 技术选型
Swift数据解析(第二篇) – Codable 上
Swift数据解析(第二篇) – Codable 下
Swift数据解析(第三篇) – Codable源码学习
Swift数据解析(第四篇) – SmartCodable 上
Swift数据解析(第四篇) – SmartCodable 下
一. 怎样找到源码
Swift的源码是公开在github上的,能够经过 Swift源码 该链接查看。
跟Codable有关的源码在这两个文件中 Codable.swift 和 JSONDecoder.swift。
二. 怎样阅览源码
我对codable相关的源码读了三遍。这是因为:
第一遍
直接读这两个文件代码,代码量太多,简直没收获。
第二遍
尝试对这两个文件进行拆分,依照类/结构体直接对相互关系,分为三大类。这才了解了各个类之间的效果。
你能够下载 Codable源码拆分
第三遍
尽管读过两遍之后,大致了解了各个类/结构体的效果。但是看到更底层的逻辑,比如带键解码容器的完结, 运用了4层规划KeyedDecodingContainerProtocol, KeyedDecodingContainer, _KeyedDecodingContainerBox, _KeyedDecodingContainerBase。 了解起来十分费劲。
我更期望知道这些 类/协议/结构体 是怎样作业的?各自承当了什么责任?这么规划的目的是什么?
所以我从最开端的调用代码开端,跟随着代码执行的次序,一步步的了解它。
三. decode进程
我们以这个简略的示例为开端:
do {
let decoder = JSONDecoder()
let feed = try decoder.decode(Person.self, from: jsonData)
print(feed)
} catch let error {
print(error)
}
JSONDecoder
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
协议。 详细代码
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办法
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
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
办法用于解码操作,匹配对应的类型然后执行条件分支。
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逻辑中
self.storage.push(container: value)
decoded = try T(from: self)
self.storage.popContainer()
container
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办法
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
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
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协议。
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解码,遇到类型不匹配的情况仍是会抛出反常?
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: 枚举值是怎样编码和解码的?
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是怎样解码的?
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书写起来很便利。这在个夸姣的体会中,会有这样的疑问: 为什么会这么便利? 协议办法是谁来完结的,怎样完结的?
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
将该代码
## SomeOne.swift 中的代码
struct SomeOne: Codable {
var name: String = ""
var age: Int = 0
}
转换成SIL中间码:
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?
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需求声明在结构体内部。