Swift底层原理-Codable

2023-05-20  本文已影响0人  祀梦_

Swift底层原理-Codable

基本用法

解码

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)

打印结果: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)!)

打印结果:{"name":"Angao","age":18,"description":"test"}

Codable

typealias Codable = Decodable & Encodable
/// 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
}

Decoder

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
}

Decode方法

open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
    let topLevel: Any
    do {
        // 对Data进行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))
    }
    
    // 创建一个内部类
    let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
    // 调用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
}

__JSONDecoder

private class __JSONDecoder : Decoder

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

init

我们先看decode方法里面调用的构造器方法: init方法,有三个参数传入

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
}
复制代码

它主要工作是

__JSONDecodingStorage

_JSONDecodingStorage是一个结构体,内部有Any类型数组可存放任意类型,提供pushpopContainer等方法,相当于一个栈容器,它是管理我们传入的container的。这里就把它理解为栈

unbox方法

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)
    }
}
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
public protocol Decodable {
    init(from decoder: Decoder) throws
}

继承Decodable协议的SIL分析

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)
}
// 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
  
  // 创建一个container变量,类型为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

  // 分配一个 CodinggKeys 内存,将 name 的枚举值写入
  %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

  // 调用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

KeyedDecodingContainer

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)
  }
  
  // 省略剩下方法
}

Decoder协议的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
}
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)
}
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    ......
}

container的decode(_:forKey:)方法

public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
    return try _box.decode(Int.self, forKey: key)
}
/// 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
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
    }

    // 省略部分方法
}
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
}
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)
}

解码容器

解码流程总结

[图片上传失败...(image-70058e-1684653950727)]

Encoder

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
}

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

Encode方法

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

__JSONEncoder

fileprivate class _JSONEncoder : Encoder

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

}

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

总结

[图片上传失败...(image-e2f0b7-1684653950727)]

上一篇 下一篇

猜你喜欢

热点阅读