Swift底層原理-Codable

Swift底層原理-Codable

  • Swift 4.0 支持了一個(gè)新的語(yǔ)言特性—Codable,其提供了一種非常簡(jiǎn)單的方式支持模型和數(shù)據(jù)之間的轉(zhuǎn)換。
  • Codable能夠?qū)⒊绦騼?nèi)部的數(shù)據(jù)結(jié)構(gòu)序列化成可交換數(shù)據(jù),也能夠?qū)⑼ㄓ脭?shù)據(jù)格式反序列化為內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu),大大提升對(duì)象和其表示之間互相轉(zhuǎn)換的體驗(yàn)。

基本用法

解碼

import Foundation

struct Product: Codable {
    var name: String
    var age: Int
    var description: String?
}

let json = """
{
"name": "AngaoTu",
"age": 18,
"description": "hello world"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let product = try decoder.decode(Product.self, from: json)

print(product)

打印結(jié)果:Product(name: "AngaoTu", age: 18, description: Optional("hello world"))

編碼

struct Product: Codable {
    var name: String
    var age: Int
    var description: String?
}

let product1 = Product(name: "Angao", age: 18, description: "test")
let encoder = JSONEncoder()
let data = try encoder.encode(product1)
print(String(data: data, encoding: .utf8)!)

打印結(jié)果:{"name":"Angao","age":18,"description":"test"}

Codable

  • Codable的定義如下:
typealias Codable = Decodable & Encodable
  • 它是DecodableEncodable協(xié)議的類(lèi)型別名。當(dāng)Codable用作類(lèi)型或泛型約束時(shí),它匹配符合這兩種協(xié)議的任何類(lèi)型。
/// A type that can encode itself to an external representation.
public protocol Encodable {
    func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {
    init(from decoder: Decoder) throws
}
  • Encodable 協(xié)議要求目標(biāo)模型必須提供編碼方法 func encode(from encoder: Encoder),從而按照指定的邏輯進(jìn)行編碼。
  • Decodable 協(xié)議要求目標(biāo)模型必須提供解碼方法 func init(from decoder: Decoder),從而按照指定的邏輯進(jìn)行解碼。

Decoder

  • 在上面解碼的時(shí)候,初始化了一個(gè)JSONDecoder對(duì)象,并調(diào)用了decode<T : Decodable>(_ type: T.Type, from data: Data)方法。
  • 我們先看一下JSONDecoder的定義

JSONDecoder

open class JSONDecoder {

    /// The strategy to use for decoding `Date` values.
    public enum DateDecodingStrategy {
        case deferredToDate
        case secondsSince1970
        case millisecondsSince1970
        @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
        case iso8601
        case formatted(DateFormatter)
        case custom((_ decoder: Decoder) throws -> Date)
    }

    /// The strategy to use for decoding `Data` values.
    public enum DataDecodingStrategy {
        case deferredToData
        case base64
        case custom((_ decoder: Decoder) throws -> Data)
    }

    /// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN).
    public enum NonConformingFloatDecodingStrategy {
        case `throw`
        case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
    }

    /// The strategy to use for automatically changing the value of keys before decoding.
    public enum KeyDecodingStrategy {
        case useDefaultKeys
        case convertFromSnakeCase
        case custom((_ codingPath: [CodingKey]) -> CodingKey)
    }

    /// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
    open var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy

    /// The strategy to use in decoding binary data. Defaults to `.base64`.
    open var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy

    /// The strategy to use in decoding non-conforming numbers. Defaults to `.throw`.
    open var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy

    /// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
    open var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy

    /// Contextual user-provided information for use during decoding.
    open var userInfo: [CodingUserInfoKey : Any]

    /// Set to `true` to allow parsing of JSON5. Defaults to `false`.
    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
    open var allowsJSON5: Bool

    /// Set to `true` to assume the data is a top level Dictionary (no surrounding "{ }" required). Defaults to `false`. Compatible with both JSON5 and non-JSON5 mode.
    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
    open var assumesTopLevelDictionary: Bool

    fileprivate struct _Options {
        let dateDecodingStrategy: DateDecodingStrategy
        let dataDecodingStrategy: DataDecodingStrategy
        let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
        let keyDecodingStrategy: KeyDecodingStrategy
        let userInfo: [CodingUserInfoKey : Any]
    }

    /// The options set on the top-level decoder.
    fileprivate var options: _Options {
        return _Options(dateDecodingStrategy: dateDecodingStrategy,
                        dataDecodingStrategy: dataDecodingStrategy,
                        nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
                        keyDecodingStrategy: keyDecodingStrategy,
                        userInfo: userInfo)
    }
    
    /// Initializes `self` with default strategies.
    public init()

    open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}
  • 根據(jù)這個(gè)結(jié)構(gòu),我們可以看出,它主要由兩部分組成

    • 一些數(shù)據(jù)類(lèi)型的解碼策略
    • decode方法
  • DataDecodingStrategy:二進(jìn)制解碼策略

    • deferredToData:默認(rèn)解碼策略

    • base64:使用base64解碼

    • custom:自定義方式解碼

  • NonConformingFloatDecodingStrategy:不合法浮點(diǎn)數(shù)的編碼策略

    • throw

    • convertFromString

  • KeyDecodingStrategyKey的編碼策略

    • useDefaultKeys

    • convertFromSnakeCase

    • custom

Decode方法

  • decode方法用于將JSON轉(zhuǎn)為指定類(lèi)型,接收T.Type類(lèi)型和Data數(shù)據(jù)
  • 我們看一下decode方法做了哪些操作
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
    let topLevel: Any
    do {
        // 對(duì)Data進(jìn)行Json序列化
        topLevel = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
    } catch {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
    }
    
    // 創(chuàng)建一個(gè)內(nèi)部類(lèi)
    let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
    // 調(diào)用unbox方法,解碼
    guard let value = try decoder.unbox(topLevel, as: type) else {
        throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
    }

    return value
}
  • 該方法主要做了以下操作

    1. 使用JSONSerializationdata數(shù)據(jù)序列化為字典的KeyValue
    2. 調(diào)用內(nèi)部類(lèi)_JSONDecoder傳入字典和編碼策略返回decoder對(duì)象
    3. 通過(guò)decoder對(duì)象的unbox方法解碼并返回value
  • 這里重點(diǎn)是創(chuàng)建了一個(gè)__JSONDecoder內(nèi)部類(lèi),讓它來(lái)完成解碼操作

__JSONDecoder

  • 我們先查看它的定義
private class __JSONDecoder : Decoder
  • 這里Decoder是一個(gè)協(xié)議

Decoder

public protocol Decoder {
  /// The path of coding keys taken to get to this point in decoding.
  var codingPath: [CodingKey] { get }

  /// Any contextual information set by the user for decoding.
  var userInfo: [CodingUserInfoKey: Any] { get }

  func container<Key>(
    keyedBy type: Key.Type
  ) throws -> KeyedDecodingContainer<Key>

  func unkeyedContainer() throws -> UnkeyedDecodingContainer

  func singleValueContainer() throws -> SingleValueDecodingContainer
}
  • Decoder 協(xié)議要求編碼器必須提供 3 中類(lèi)型的解碼 container、解碼路徑、上下文緩存。

  • 接下來(lái)讓我們看一下__JSONDecoder的內(nèi)部結(jié)構(gòu)

private class __JSONDecoder : Decoder {
    // MARK: Properties

    /// The decoder's storage.
    var storage: _JSONDecodingStorage

    /// Options set on the top-level decoder.
    let options: JSONDecoder._Options

    /// The path to the current point in encoding.
    fileprivate(set) public var codingPath: [CodingKey]

    /// Contextual user-provided information for use during encoding.
    public var userInfo: [CodingUserInfoKey : Any] {
        return self.options.userInfo
    }

    // MARK: - Initialization

    /// Initializes `self` with the given top-level container and options.
    init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
        self.storage = _JSONDecodingStorage()
        self.storage.push(container: container)
        self.codingPath = codingPath
        self.options = options
    }

    // MARK: - Decoder Methods

    public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
        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."))
        }

        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)
    }

    public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        guard !(self.storage.topContainer is NSNull) else {
            throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
                                              DecodingError.Context(codingPath: self.codingPath,
                                                                    debugDescription: "Cannot get unkeyed decoding container -- found null value instead."))
        }

        guard let topContainer = self.storage.topContainer as? [Any] else {
            throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer)
        }

        return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
    }

    public func singleValueContainer() throws -> SingleValueDecodingContainer {
        return self
    }
}
  • 在該類(lèi)中存儲(chǔ)了一些解碼需要用的信息,比如說(shuō)解碼策略,序列化后的keyValue

init

我們先看decode方法里面調(diào)用的構(gòu)造器方法: init方法,有三個(gè)參數(shù)傳入

  • container:序列化后的KeyValue
  • codingPathCodingKey類(lèi)型的空數(shù)組
  • options:編碼策略
fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
    self.storage = _JSONDecodingStorage()
    self.storage.push(container: container)
    self.codingPath = codingPath
    self.options = options
}
復(fù)制代碼

它主要工作是

  • 創(chuàng)建內(nèi)部類(lèi)_JSONDecodingStorage
  • 使用push方法存儲(chǔ)要解碼的數(shù)據(jù)container
  • 初始化 optionscodingPath(空數(shù)組)

__JSONDecodingStorage

_JSONDecodingStorage是一個(gè)結(jié)構(gòu)體,內(nèi)部有Any類(lèi)型數(shù)組可存放任意類(lèi)型,提供push、popContainer等方法,相當(dāng)于一個(gè)棧容器,它是管理我們傳入的container的。這里就把它理解為棧

unbox方法

  • unbox方法用于解碼操作,匹配對(duì)應(yīng)的類(lèi)型然后執(zhí)行條件分支
func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
    return try unbox_(value, as: type) as? T
}

func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
    if type == Date.self || type == NSDate.self {
        return try self.unbox(value, as: Date.self)
    } else if type == Data.self || type == NSData.self {
        return try self.unbox(value, as: Data.self)
    } else if type == URL.self || type == 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."))
        }
        return url
    } else if type == Decimal.self || type == NSDecimalNumber.self {
        return try self.unbox(value, as: Decimal.self)
    } else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
        return try self.unbox(value, as: stringKeyedDictType)
    } else {
        self.storage.push(container: value)
        defer { self.storage.popContainer() }
        return try type.init(from: self)
    }
}
  • 該方法內(nèi)部調(diào)用了unbox_方法
    • Date、Data、URL等,會(huì)單獨(dú)調(diào)用各自的unbox方法,因?yàn)樯婕暗角懊嬲f(shuō)的解析策略,
    • 我們自己聲明的類(lèi)或者結(jié)構(gòu)體,回來(lái)到最后一個(gè)分支type.init(from: self)
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
  • 源碼中type.init(from:)方法,傳入的self,本質(zhì)是_JSONDecoder;type就是我們要解析的value的類(lèi)型
  • 那么init(from:)即應(yīng)該是我們Decodable協(xié)議中的init方法
public protocol Decodable {
    init(from decoder: Decoder) throws
}
  • 那這里有個(gè)疑問(wèn),我們前面都是只繼承了Decodable,好像從來(lái)沒(méi)有實(shí)現(xiàn)過(guò) init(from decoder: Decoder) throws它在哪里實(shí)現(xiàn)的呢?

繼承Decodable協(xié)議的SIL分析

  • 通過(guò)對(duì)JSONDecoder源碼的分析,已經(jīng)得知,除了幾個(gè)特殊的類(lèi)型外,最后走的都是遵守Decodableinit(from:)方法,那在swiftDecodable源碼中,是沒(méi)有針對(duì) T(from: self) 的實(shí)現(xiàn)的。所以,我們通過(guò)底層的sil的代碼去窺探 T(from: self) 的實(shí)現(xiàn)。
struct Product : Decodable & Encodable {
  @_hasStorage var name: String { get set }
  @_hasStorage var age: Int { get set }
  @_hasStorage @_hasInitialValue var description: String? { get set }
  enum CodingKeys : CodingKey {
    case name
    case age
    case description
    @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Product.CodingKeys, _ b: Product.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(from decoder: Decoder) throws
  init(name: String, age: Int, description: String? = nil)
}
  • 我們可以看到編譯器自動(dòng)幫我們生成了CodingKeys枚舉類(lèi)型,并遵循CodingKey協(xié)議。解碼過(guò)程中會(huì)通過(guò)CodingKeys找到對(duì)應(yīng)case

  • 自動(dòng)實(shí)現(xiàn)decode解碼方法:init(from decoder: Decoder)

  • 這里我們著重看一下編譯器默認(rèn)給我們的實(shí)現(xiàn)

// Product.init(from:)
sil hidden [ossa] @$s4main7ProductV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Product.Type) -> (@owned Product, @error Error) {
// %0 "decoder"                                   // users: %82, %60, %12, %5
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin Product.Type):
  %2 = alloc_box ${ var Product }, var, name "self" // user: %3
  %3 = mark_uninitialized [rootself] %2 : ${ var Product } // users: %83, %61, %4
  %4 = project_box %3 : ${ var Product }, 0       // users: %59, %53, %40, %27, %7
  debug_value %0 : $*Decoder, let, name "decoder", argno 1, implicit, expr op_deref // id: %5
  debug_value undef : $Error, var, name "$error", argno 2 // id: %6
  %7 = struct_element_addr %4 : $*Product, #Product.description // user: %10
  // function_ref variable initialization expression of Product.description
  %8 = function_ref @$s4main7ProductV11descriptionSSSgvpfi : $@convention(thin) () -> @owned Optional<String> // user: %9
  %9 = apply %8() : $@convention(thin) () -> @owned Optional<String> // user: %10
  store %9 to [init] %7 : $*Optional<String>      // id: %10
  
  // 創(chuàng)建一個(gè)container變量,類(lèi)型為KeyedDecodingContainer
  %11 = alloc_stack [lexical] $KeyedDecodingContainer<Product.CodingKeys>, let, name "container", implicit // users: %58, %57, %50, %79, %78, %37, %74, %73, %24, %69, %68, %16, %64
  %12 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("1F24E7D6-5FE6-11ED-B61E-86E79974171B") Decoder // users: %16, %16, %15
  %13 = metatype $@thin Product.CodingKeys.Type
  %14 = metatype $@thick Product.CodingKeys.Type  // user: %16
  
  // 獲取 __JSONDecoder 的 container 方法的地址
  %15 = witness_method $@opened("1F24E7D6-5FE6-11ED-B61E-86E79974171B") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %12 : $*@opened("1F24E7D6-5FE6-11ED-B61E-86E79974171B") Decoder : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error Error) // type-defs: %12; user: %16
  try_apply %15<@opened("1F24E7D6-5FE6-11ED-B61E-86E79974171B") Decoder, Product.CodingKeys>(%11, %14, %12) : $@convention(witness_method: Decoder) <τ_0_0 where τ_0_0 : Decoder><τ_1_0 where τ_1_0 : CodingKey> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> (@out KeyedDecodingContainer<τ_1_0>, @error Error), normal bb1, error bb5 // type-defs: %12; id: %16

bb1(%17 : $()):                                   // Preds: bb0
  %18 = metatype $@thin String.Type               // user: %24
  %19 = metatype $@thin Product.CodingKeys.Type

  // 分配一個(gè) CodinggKeys 內(nèi)存,將 name 的枚舉值寫(xiě)入
  %20 = enum $Product.CodingKeys, #Product.CodingKeys.name!enumelt // user: %22
  %21 = alloc_stack $Product.CodingKeys           // users: %26, %24, %67, %22
  store %20 to [trivial] %21 : $*Product.CodingKeys // id: %22

  // 調(diào)用KeyedDecodingContainer 中的 decode(_:forKey:) 方法,即 __JSONKeyedDecodingContainer 的 decode(_:forKey:)
  // function_ref KeyedDecodingContainer.decode(_:forKey:)
  %23 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2Sm_xtKF : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin String.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@owned String, @error Error) // user: %24
  try_apply %23<Product.CodingKeys>(%18, %21, %11) : $@convention(method) <τ_0_0 where τ_0_0 : CodingKey> (@thin String.Type, @in_guaranteed τ_0_0, @in_guaranteed KeyedDecodingContainer<τ_0_0>) -> (@owned String, @error Error), normal bb2, error bb6 // id: %24
  • 這里面代碼比較長(zhǎng),我們直接看重點(diǎn),它其實(shí)做了3件事
    1. %11行,創(chuàng)建了一個(gè)Container:KeyedDecodingContainer
    2. %15行,從協(xié)議目擊表中,調(diào)用Decoder協(xié)議的container方法
    3. %23行,通過(guò)containerdecode(_:forKey:)方法來(lái)進(jìn)行解碼操作。

KeyedDecodingContainer

  • KeyedDecodingContainer<K>是一個(gè)結(jié)構(gòu)體,遵循KeyedDecodingContainerProtocol協(xié)議。有一個(gè)條件限制,K必須遵循CodingKey協(xié)議。
  • 結(jié)構(gòu)體內(nèi)定義各種類(lèi)型的解碼方法,會(huì)根據(jù)不同類(lèi)型匹配到對(duì)應(yīng)的decode方法
public struct KeyedDecodingContainer<K: CodingKey> :
  KeyedDecodingContainerProtocol
{
  public typealias Key = K

  /// The container for the concrete decoder.
  internal var _box: _KeyedDecodingContainerBase

  /// Creates a new instance with the given container.
  ///
  /// - parameter container: The container to hold.
  public init<Container: KeyedDecodingContainerProtocol>(
    _ container: Container
  ) where Container.Key == Key {
    _box = _KeyedDecodingContainerBox(container)
  }

  /// The path of coding keys taken to get to this point in decoding.
  public var codingPath: [CodingKey] {
    return _box.codingPath
  }

  public var allKeys: [Key] {
    return _box.allKeys as! [Key]
  }

  public func contains(_ key: Key) -> Bool {
    return _box.contains(key)
  }

  public func decodeNil(forKey key: Key) throws -> Bool {
    return try _box.decodeNil(forKey: key)
  }

  public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
    return try _box.decode(Bool.self, forKey: key)
  }


  public func decode(_ type: String.Type, forKey key: Key) throws -> String {
    return try _box.decode(String.self, forKey: key)
  }

  public func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
    return try _box.decode(Double.self, forKey: key)
  }

  public func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
    return try _box.decode(Float.self, forKey: key)
  }
  
  public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
    return try _box.decode(Int.self, forKey: key)
  }
  
  // 省略剩下方法
}
  • KeyedDecodingContainer定義了很多decodedecodeIfPresent的解析方法,其中decodeIfPresent是用在可選值身上的

Decoder協(xié)議的container方法

  • 這里傳入的decoder是我們內(nèi)部類(lèi)__JSONDecoder,知道 _JSONDecoder遵守 Decoder協(xié)議,調(diào)用它的container方法
public protocol Decoder {
    /// The path of coding keys taken to get to this point in decoding.
    var codingPath: [CodingKey] { get }

    /// Any contextual information set by the user for decoding.
    var userInfo: [CodingUserInfoKey : Any] { get }

    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey

    func unkeyedContainer() throws -> UnkeyedDecodingContainer

    func singleValueContainer() throws -> SingleValueDecodingContainer
}
  • 我們查看一下__JSONDecoder中,該協(xié)議的實(shí)現(xiàn)
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
    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."))
    }

    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)
}
  • 這里的返回值就是KeyedDecodingContainer類(lèi)型對(duì)象。

  • 所以到這里,Product.init(from:)第一件事我們已經(jīng)完成了,手動(dòng)實(shí)現(xiàn)如下

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    ......
}

container的decode(_:forKey:)方法

  • KeyedDecodingContainer中根據(jù)類(lèi)別定義了很多decode方法,我們隨便選擇一個(gè)類(lèi)型的decode方法分析
public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
    return try _box.decode(Int.self, forKey: key)
}
  • 核心方法是_box.decode()方法
  • 這里的_box是初始化方法中傳入進(jìn)來(lái)的_JSONKeyedDecodingContainer,所以上面這句話(huà)就是在調(diào)用_JSONKeyedDecodingContainerdecode方法
/// The container for the concrete decoder.
internal var _box: _KeyedDecodingContainerBase

/// Creates a new instance with the given container.
///
/// - parameter container: The container to hold.
public init<Container: KeyedDecodingContainerProtocol>(
    _ container: Container
) where Container.Key == Key {
    _box = _KeyedDecodingContainerBox(container)
}

let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
  • 那么我們看一下_JSONKeyedDecodingContainer結(jié)構(gòu)
_JSONKeyedDecodingContainer
private struct _JSONKeyedDecodingContainer<K : CodingKey> : KeyedDecodingContainerProtocol {
    typealias Key = K

    // MARK: Properties

    /// A reference to the decoder we're reading from.
    private let decoder: __JSONDecoder

    /// A reference to the container we're reading from.
    private let container: [String : Any]

    /// The path of coding keys taken to get to this point in decoding.
    private(set) public var codingPath: [CodingKey]

    // MARK: - Initialization

    /// Initializes `self` by referencing the given decoder and container.
    init(referencing decoder: __JSONDecoder, wrapping container: [String : Any]) {
        self.decoder = decoder
        switch decoder.options.keyDecodingStrategy {
        case .useDefaultKeys:
            self.container = container
        case .convertFromSnakeCase:
            // Convert the snake case keys in the container to camel case.
            // If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with JSON dictionaries.
            self.container = Dictionary(container.map {
                key, value in (JSONDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value)
            }, uniquingKeysWith: { (first, _) in first })
        case .custom(let converter):
            self.container = Dictionary(container.map {
                key, value in (converter(decoder.codingPath + [_JSONKey(stringValue: key, intValue: nil)]).stringValue, value)
            }, uniquingKeysWith: { (first, _) in first })
        }
        self.codingPath = decoder.codingPath
    }

    // MARK: - KeyedDecodingContainerProtocol Methods

    public var allKeys: [Key] {
        return self.container.keys.compactMap { Key(stringValue: $0) }
    }

    public func contains(_ key: Key) -> Bool {
        return self.container[key.stringValue] != nil
    }

    private func _errorDescription(of key: CodingKey) -> String {
        switch decoder.options.keyDecodingStrategy {
        case .convertFromSnakeCase:
            // In this case we can attempt to recover the original value by reversing the transform
            let original = key.stringValue
            let converted = JSONEncoder.KeyEncodingStrategy._convertToSnakeCase(original)
            let roundtrip = JSONDecoder.KeyDecodingStrategy._convertFromSnakeCase(converted)
            if converted == original {
                return "\(key) (\"\(original)\")"
            } else if roundtrip == original {
                return "\(key) (\"\(original)\"), converted to \(converted)"
            } else {
                return "\(key) (\"\(original)\"), with divergent representation \(roundtrip), converted to \(converted)"
            }
        default:
            // Otherwise, just report the converted string
            return "\(key) (\"\(key.stringValue)\")"
        }
    }
    
    public func decodeNil(forKey key: Key) throws -> Bool {
        guard let entry = self.container[key.stringValue] else {
            throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
        }

        return entry is NSNull
    }

    public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
        guard let entry = self.container[key.stringValue] else {
            throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
        }

        self.decoder.codingPath.append(key)
        defer { self.decoder.codingPath.removeLast() }

        guard let value = try self.decoder.unbox(entry, as: Bool.self) else {
            throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
        }

        return value
    }

    // 省略部分方法
}
  • 我們可以看到它也按照不同類(lèi)別調(diào)用不同的decode方法,隨便找一個(gè)方法
public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
    guard let entry = self.container[key.stringValue] else {
        throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
    }

    self.decoder.codingPath.append(key)
    defer { self.decoder.codingPath.removeLast() }

    guard let value = try self.decoder.unbox(entry, as: Bool.self) else {
        throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
    }

    return value
}
  • 核心方法是self.decoder.unbox(entry, as: Bool.self),這里的self.decoder就是我們上面?zhèn)鬟M(jìn)來(lái)的_JSONDecoder,轉(zhuǎn)了一圈,最后還是調(diào)用的是_JSONDecoderunbox方法

  • 最后我們可以基本的出Product.init(from:)的實(shí)現(xiàn)

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .nickname)
    age = try container.decode(Int.self, forKey: .age)
}

解碼容器

  • 我們?cè)?code>Decoder協(xié)議中知道,一共提供了三種Container:KeyedDecodingContainer,UnkeyedDecodingContainer,SingleValueDecodingContainer

  • KeyedDecodingContainer 類(lèi)似于字典,鍵值對(duì)容器,鍵值是強(qiáng)類(lèi)型。

  • UnkeyedDecodingContainer 類(lèi)似于數(shù)組,連續(xù)值容器,沒(méi)有鍵值。

  • SingleValueDecodingContainer 基礎(chǔ)數(shù)據(jù)類(lèi)型容器。

解碼流程總結(jié)

[圖片上傳失敗...(image-70058e-1684653950727)]

Encoder

  • Encodable 協(xié)議要求目標(biāo)模型必須提供編碼方法 func encode(from encoder: Encoder),從而按照指定的邏輯進(jìn)行編碼。
public protocol Encodable {

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}
  • 在編碼時(shí),我們需要?jiǎng)?chuàng)建一個(gè)JSONEncoder對(duì)象,通過(guò)調(diào)用它的encode方法

JSONEncoder

@_objcRuntimeName(_TtC10Foundation13__JSONEncoder)
open class JSONEncoder {
    // MARK: Options

    /// The formatting of the output JSON data.
    public struct OutputFormatting : OptionSet {
        /// The format's default value.
        public let rawValue: UInt

        public init(rawValue: UInt) {
            self.rawValue = rawValue
        }

        public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0)

        @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *)
        public static let sortedKeys    = OutputFormatting(rawValue: 1 << 1)

        @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
        public static let withoutEscapingSlashes = OutputFormatting(rawValue: 1 << 3)
    }

    /// The strategy to use for encoding `Date` values.
    public enum DateEncodingStrategy {
        case deferredToDate
        case secondsSince1970
        case millisecondsSince1970
        @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
        case iso8601
        case formatted(DateFormatter)
        case custom((Date, Encoder) throws -> Void)
    }

    /// The strategy to use for encoding `Data` values.
    public enum DataEncodingStrategy {
        case deferredToData
        case base64
        case custom((Data, Encoder) throws -> Void)
    }

    /// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN).
    public enum NonConformingFloatEncodingStrategy {
        case `throw`
        case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String)
    }

    /// The strategy to use for automatically changing the value of keys before encoding.
    public enum KeyEncodingStrategy {
        /// Use the keys specified by each type. This is the default strategy.
        case useDefaultKeys
        
        case convertToSnakeCase
    
        case custom((_ codingPath: [CodingKey]) -> CodingKey)
        
        fileprivate static func _convertToSnakeCase(_ stringKey: String) -> String {...}
    }

    /// The output format to produce. Defaults to `[]`.
    open var outputFormatting: OutputFormatting = []

    /// The strategy to use in encoding dates. Defaults to `.deferredToDate`.
    open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate

    /// The strategy to use in encoding binary data. Defaults to `.base64`.
    open var dataEncodingStrategy: DataEncodingStrategy = .base64

    /// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`.
    open var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy = .throw

    /// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`.
    open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
    
    /// Contextual user-provided information for use during encoding.
    open var userInfo: [CodingUserInfoKey : Any] = [:]

    /// Options set on the top-level encoder to pass down the encoding hierarchy.
    fileprivate struct _Options {
        let dateEncodingStrategy: DateEncodingStrategy
        let dataEncodingStrategy: DataEncodingStrategy
        let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
        let keyEncodingStrategy: KeyEncodingStrategy
        let userInfo: [CodingUserInfoKey : Any]
    }

    /// The options set on the top-level encoder.
    fileprivate var options: _Options {
        return _Options(dateEncodingStrategy: dateEncodingStrategy,
                        dataEncodingStrategy: dataEncodingStrategy,
                        nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
                        keyEncodingStrategy: keyEncodingStrategy,
                        userInfo: userInfo)
    }

    // MARK: - Constructing a JSON Encoder

    /// Initializes `self` with default strategies.
    public init() {}


    open func encode<T : Encodable>(_ value: T) throws -> Data {
        let encoder = __JSONEncoder(options: self.options)

        guard let topLevel = try encoder.box_(value) else {
            throw EncodingError.invalidValue(value, 
                                             EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
        }

        let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)
        do {
           return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
        } catch {
            throw EncodingError.invalidValue(value, 
                                             EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
        }
    }
}
  • 根據(jù)這個(gè)結(jié)構(gòu),我們可以看出它和JSONEncoder一樣,它主要由兩部分組成
    • 一些數(shù)據(jù)類(lèi)型的編碼策略
    • encode方法

Encode方法

  • encode方法用于將指定類(lèi)型轉(zhuǎn)為JSON,接收T.Type類(lèi)型
open func encode<T : Encodable>(_ value: T) throws -> Data {
    let encoder = __JSONEncoder(options: self.options)

    guard let topLevel = try encoder.box_(value) else {
        throw EncodingError.invalidValue(value, 
                                         EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
    }

    let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)
    do {
        return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
    } catch {
        throw EncodingError.invalidValue(value, 
                                         EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
    }
}
  • 這個(gè)流程剛好與Decoder是相反的
    • 創(chuàng)建內(nèi)部類(lèi)_JSONEncoder
    • 調(diào)用box_方法包裝成字典類(lèi)型
    • 使用JSONSerialization序列化為Data數(shù)據(jù)

__JSONEncoder

  • 我們看一下它的定義
fileprivate class _JSONEncoder : Encoder
  • 這里Encoder是一個(gè)協(xié)議

Encoder

public protocol Encoder {

    /// The path of coding keys taken to get to this point in encoding.
    var codingPath: [CodingKey] { get }

    /// Any contextual information set by the user for encoding.
    var userInfo: [CodingUserInfoKey : Any] { get }

    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey

    func unkeyedContainer() -> UnkeyedEncodingContainer

    func singleValueContainer() -> SingleValueEncodingContainer
}
  • Encoder 協(xié)議要求解碼器必須提供 3 中類(lèi)型的編碼 container、解碼路徑、上下文緩存。

  • 接下來(lái)讓我們看一下__JSONEncoder的內(nèi)部結(jié)構(gòu)

fileprivate class _JSONEncoder : Encoder {
    fileprivate var storage: _JSONEncodingStorage

    fileprivate let options: JSONEncoder._Options

    public var codingPath: [CodingKey]

    public var userInfo: [CodingUserInfoKey : Any] {
        return self.options.userInfo
    }

    fileprivate init(options: JSONEncoder._Options, codingPath: [CodingKey] = []) {
        self.options = options
        self.storage = _JSONEncodingStorage()
        self.codingPath = codingPath
    }

    fileprivate var canEncodeNewValue: Bool {
        return self.storage.count == self.codingPath.count
    }

    public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
        let topContainer: NSMutableDictionary
        if self.canEncodeNewValue {
            topContainer = self.storage.pushKeyedContainer()
        } else {
            guard let container = self.storage.containers.last as? NSMutableDictionary else {
                preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
            }

            topContainer = container
        }

        let container = _JSONKeyedEncodingContainer<Key>(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
        return KeyedEncodingContainer(container)
    }

    public func unkeyedContainer() -> UnkeyedEncodingContainer {
        let topContainer: NSMutableArray
        if self.canEncodeNewValue {
            topContainer = self.storage.pushUnkeyedContainer()
        } else {
            guard let container = self.storage.containers.last as? NSMutableArray else {
                preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.")
            }

            topContainer = container
        }

        return _JSONUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
    }

    public func singleValueContainer() -> SingleValueEncodingContainer {
        return self
    }

}
  • 它里面的初始化方法、以及存儲(chǔ)的屬性與__JSONDecoder類(lèi)似,這里省略分析過(guò)程,讀者可以自已對(duì)照分析。我們直接進(jìn)入最直接的box方法

box方法

func box(_ value: Encodable) throws -> NSObject {
    return try self.box_(value) ?? NSDictionary()
}

func box_(_ value: Encodable) throws -> NSObject? {
    // Disambiguation between variable and function is required due to
    // issue tracked at: https://bugs.swift.org/browse/SR-1846
    let type = Swift.type(of: value)
    if type == Date.self || type == NSDate.self {
        // Respect Date encoding strategy
        return try self.box((value as! Date))
    } else if type == Data.self || type == NSData.self {
        // Respect Data encoding strategy
        return try self.box((value as! Data))
    } else if type == URL.self || type == NSURL.self {
        // Encode URLs as single strings.
        return self.box((value as! URL).absoluteString)
    } else if type == Decimal.self || type == NSDecimalNumber.self {
        // JSONSerialization can natively handle NSDecimalNumber.
        return (value as! NSDecimalNumber)
    } else if value is _JSONStringDictionaryEncodableMarker {
        return try self.box(value as! [String : Encodable])
    }

    // The value should request a container from the __JSONEncoder.
    let depth = self.storage.count
    do {
        try value.encode(to: self)
    } catch {
        // If the value pushed a container before throwing, pop it back off to restore state.
        if self.storage.count > depth {
            let _ = self.storage.popContainer()
        }

        throw error
    }

    // The top container should be a new container.
    guard self.storage.count > depth else {
        return nil
    }

    return self.storage.popContainer()
}
  • box_方法,根據(jù)value的不同類(lèi)型,調(diào)用不同的代碼分支,將value包裝成對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。

  • 如果value不是上述定義的數(shù)據(jù)類(lèi)型,最終會(huì)調(diào)用value.encode(to: self)方法,傳入的self就是_JSONEncoder

  • 一樣這里我們沒(méi)有提供自定義類(lèi)的encode實(shí)現(xiàn),還是通過(guò)編譯器自動(dòng)幫我們實(shí)現(xiàn)了。具體分析和解碼編譯成sil文件一致,感興趣讀者可以自行研究。

總結(jié)

  • 這里分析比較粗略,如果你已經(jīng)了解解碼過(guò)程,這個(gè)過(guò)程通過(guò)類(lèi)比非常簡(jiǎn)單可以明白。

[圖片上傳失敗...(image-e2f0b7-1684653950727)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容