Swift底層原理-Codable
-
Swift4.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
- 它是
Decodable和Encodable協(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ù)的編碼策略throwconvertFromString
-
KeyDecodingStrategy:Key的編碼策略useDefaultKeysconvertFromSnakeCasecustom
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
}
-
該方法主要做了以下操作
- 使用
JSONSerialization將data數(shù)據(jù)序列化為字典的KeyValue - 調(diào)用內(nèi)部類(lèi)
_JSONDecoder傳入字典和編碼策略返回decoder對(duì)象 - 通過(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 -
codingPath:CodingKey類(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 - 初始化
options和codingPath(空數(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)型外,最后走的都是遵守Decodable的init(from:)方法,那在swift的Decodable源碼中,是沒(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件事
- 在
%11行,創(chuàng)建了一個(gè)Container:KeyedDecodingContainer - 在
%15行,從協(xié)議目擊表中,調(diào)用Decoder協(xié)議的container方法 - 在
%23行,通過(guò)container的decode(_: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定義了很多decode和decodeIfPresent的解析方法,其中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)用_JSONKeyedDecodingContainer的decode方法
/// 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)用的是_JSONDecoder的unbox方法最后我們可以基本的出
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,SingleValueDecodingContainerKeyedDecodingContainer類(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ù)
- 創(chuàng)建內(nèi)部類(lèi)
__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)]