这是Swift数据解析计划的系列文章:

Swift数据解析(第一篇) – 技术选型

Swift数据解析(第二篇) – Codable 上

Swift数据解析(第二篇) – Codable 下

Swift数据解析(第三篇) – Codable源码学习

Swift数据解析(第四篇) – SmartCodable 上

Swift数据解析(第四篇) – SmartCodable 下

一. 怎样找到源码

Swift的源码是公开在github上的,能够经过 Swift源码 该链接查看。

跟Codable有关的源码在这两个文件中 Codable.swift 和 JSONDecoder.swift。

二. 怎样阅览源码

我对codable相关的源码读了三遍。这是因为:

第一遍

直接读这两个文件代码,代码量太多,简直没收获。

第二遍

尝试对这两个文件进行拆分,依照类/结构体直接对相互关系,分为三大类。这才了解了各个类之间的效果。

你能够下载 Codable源码拆分

Swift数据解析(第三篇) - 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)
}

Swift数据解析(第三篇) - Codable源码学习

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 完结。

  1. 入参的泛型 T 必须遵从 Decodable 协议。
  2. 运用 JSONSerializationdata 数据序列化。
  3. 调用内部类 _JSONDecoder ,依赖数据字典和编码策略生成 decoder 目标。
  4. 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, 并存储当前的解码数据。别离初始化 optionscodingPath

_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 Stackfileprivate 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中,运用decodeIfPresentdecode的区别在于处理的特点是否可选值(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编译器还包括新的高档其他中间言语,称为SILSIL会对Swift进行较高档其他语义剖析和优化。

Swift数据解析(第三篇) - Codable源码学习

能够经过 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) throwsinit(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需求声明在结构体内部。