Swift底層探索:Codable

Swift 4.0后引入的特性甸各,目標(biāo)是取代NSCoding協(xié)議。對結(jié)構(gòu)體焰坪,枚舉和類都支持趣倾,能夠把JSON這種弱類型數(shù)據(jù)轉(zhuǎn)換成代碼中使用的強類型數(shù)據(jù),同時由于編譯器的幫助某饰,可以少寫很多重復(fù)代碼儒恋。
Codable協(xié)議被設(shè)計出來用于替代NSCoding協(xié)議善绎,所以遵從Codable協(xié)議的對象就可以無縫的支持NSKeyedArchiver和NSKeyedUnarchiver對象進(jìn)行Archive&UnArchive持久化和反持久化。原有的解碼過程和持久化過程需要單獨處理诫尽,現(xiàn)在通過新的Codable協(xié)議一起搞定禀酱。

Codable定義:(由DecodableEncodable協(xié)議組成):

public typealias Codable = Decodable & Encodable

Decodable協(xié)議定義了一個初始化函數(shù):init,遵從Decodable協(xié)議的類型可以使用任何Decoder對象進(jìn)行初始化,完成解碼過程。

public protocol Decodable {
    init(from decoder: Decoder) throws
}

Encodable協(xié)議定義了一個encode方法, 任何Encoder對象都可以創(chuàng)建遵從了Encodable協(xié)議類型的表示箱锐,完成編碼過程比勉。

public protocol Encodable {
    func encode(to encoder: Encoder) throws
}

Swift標(biāo)準(zhǔn)庫中的類型StringInt驹止、Double以及Foundation 框架中Data浩聋、DateURL都是默認(rèn)支持Codable協(xié)議的臊恋,只需聲明支持協(xié)議即可褂乍。

基礎(chǔ)語法

一個最簡單的例子:

struct Hotpot: Codable{
    var name: String
    var age: Int
    var height: Double
}

Hotpot遵循了Codable協(xié)議撒璧,他就默認(rèn)有了init(from:)encode(to:)方法。
原始數(shù)據(jù)(為了演示方便以字符串代替):

let jsonString = """
{
    "age": 10,
    "name": "cat",
    "height": 1.85
}
"""

解碼

let jsonData = jsonString.data(using: .utf8)
//創(chuàng)建解碼器
let jsonDecoder = JSONDecoder()
if let data = jsonData {
    //解碼,參數(shù):數(shù)據(jù)類型,原始data數(shù)據(jù)
    let hotpot = try? jsonDecoder.decode(Hotpot.self, from: data)
    print(hotpot ?? "error")
}
//輸出
Hotpot(name: "cat", age: 10, height: 1.85)
  • 創(chuàng)建解碼器
  • 解碼decode傳入類型(Hotpot.self)和數(shù)據(jù)(data

編碼

let jsonEncode = JSONEncoder()
let jsonData1 = try? jsonEncode.encode(hotpot)
if let data = jsonData1 {
    let jsonString1 = String(decoding: data, as: UTF8.self)
    print(jsonString1)
}
//輸出
{"name":"cat","age":10,"height":1.8500000000000001}

這里發(fā)現(xiàn)在encode的時候精度出現(xiàn)了問題:
解決方案大致分為以下幾種:

  • 浮點數(shù)轉(zhuǎn)string處理逮壁。在encode時候double值轉(zhuǎn)為String就能避免這個問題。
  • 使用NSDecimalNumber來接收艳悔,由于NSDecimalNumber無法遵循Codable協(xié)議(原因)集嵌,所以需要用Decimal來接收數(shù)據(jù),NSDecimalNumber作為計算屬性來處理數(shù)據(jù)放吩。
  • 三方庫 金額計算智听。

對于第二點:
Synthesizing conformace to Codable, Equatable and Hashable in different source files is currently not supported by the Swift compiler。
At the moment (Xcode 10.2.1 / Swift 5.0.1) Codable currently isn't supported yet if an extension in one file adds conformance in a different file. Check this out at https://bugs.swift.org/. https://bugs.swift.org/browse/SR-6101
大概意思是目前Swift不支持在不同的源文件渡紫,合成conformaceCodable到推、EquatableHashable
什么意思呢惕澎?看個例子就明白了:
A文件中定義一個class莉测,在B文件擴展這個class遵循協(xié)議codable就會報錯了。

//A文件:
class HPTestCodable {    
}
//B文件:
extension HPTestCodable:Codable {
}
image.png

解決精度問題
以第二種方式為例唧喉,這里僅僅是為了解決Encode過程中height精度問題:

struct Hotpot: Codable{
    var name: String
    var age: Int
    var height: Decimal
    var heightNumber: NSDecimalNumber {
            get {
                return NSDecimalNumber(decimal: height)
            }
            set {
                height = newValue.decimalValue
            }
        }
}

調(diào)用:

/**json數(shù)據(jù)*/
let jsonString = """
{
    "age": 10,
    "name": "cat",
    "height": 1.85
}
"""
let jsonData = jsonString.data(using: .utf8)

/**解碼*/
var hotpot:Hotpot?
if let data = jsonData {
    //解碼,參數(shù):數(shù)據(jù)類型捣卤,原始data數(shù)據(jù)
    hotpot = try? JSONDecoder().decode(Hotpot.self, from: data)
    if let height = hotpot?.height.description {
        print(height)
    }
    print(hotpot ?? "error")
}
/**編碼*/
let jsonEncode = JSONEncoder()
let jsonData1 = try? jsonEncode.encode(hotpot)
if let data = jsonData1 {
    let jsonString = String(decoding: data, as: UTF8.self)
    print(jsonString)
}

結(jié)果:

1.85
Hotpot(name: "cat", age: 10, height: 1.85)
{"name":"cat","age":10,"height":1.85}

這里看到decode、encode欣喧、取值精度都沒有問題了腌零。

歸檔反歸檔
由于JSONEncoder將數(shù)據(jù)轉(zhuǎn)換成了data,我們可以直接對數(shù)據(jù)進(jìn)行存儲了唆阿,可以寫入本地文件益涧、數(shù)據(jù)庫等。以下簡單寫入UserDefaults演示(當(dāng)然用NSKeyedArchiver也可以):

if let data = try? JSONEncoder().encode(hotpot) {
    let jsonString = String(decoding: data, as: UTF8.self)
   //寫入UserDefaults
    UserDefaults.standard.set(data, forKey: "hotpotData")
    print(jsonString)
}
// 從UserDefaults讀取
if let data = UserDefaults.standard.data(forKey: "hotpotData") {
    let jsonString = String(decoding: data, as: UTF8.self)
    print(jsonString)
}
{"name":"cat","age":10,"height":1.85}
{"name":"cat","age":10,"height":1.85}

嵌套的模型

struct Hotpot: Codable{
    var name: String
    var age: Int
    var height: Double
    var cat: Cat
}

extension Hotpot {
    struct Cat: Codable {
        var name: String
        var age: Int
    }
}

let jsonString = """
{
    "name": "hotpot",
    "height": 1.85,
    "age": 18,
    "cat": {
        "age": 1,
        "name": "cat"
     }
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData {
    let result = try? decoder.decode(Hotpot.self, from: data)
    print(result ?? "解析失敗")
}

輸出:

Hotpot(name: "hotpot", age: 18, height: 1.85, cat: SwiftProtocol.Hotpot.Cat(name: "cat", age: 1))
  • 對于遵循了Codable協(xié)議的類型驯鳖,解碼器能夠自動識別模型的嵌套闲询。

包含數(shù)組

struct Hotpot: Codable{
    var name: String
    var age: Int
    var height: Double
    var cat: [Cat]
}

extension Hotpot {
    struct Cat: Codable {
        var name: String
        var age: Int
    }
}

let jsonString = """
{
    "name": "hotpot",
    "height": 1.85,
    "age": 18,
    "cat": [{
        "age": 1,
        "name": "cat"
     },{
        "age": 10,
        "name": "cat1"
     }]
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
    let result = try? decoder.decode(Hotpot.self, from: data)
    print(result ?? "解析失敗")
}
Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])

可以看到不需要做額外的操作久免。

JSON數(shù)據(jù)是數(shù)組集合

let jsonString = """
[
  {
    "age" : 18,
    "cat" : [
      {
        "age" : 1,
        "name" : "cat"
      },
      {
        "age" : 10,
        "name" : "cat1"
      }
    ],
    "name" : "hotpot",
    "height" : 1.85
  },
  {
    "age" : 18,
    "cat" : [
      {
        "age" : 1,
        "name" : "cat"
      },
      {
        "age" : 10,
        "name" : "cat1"
      }
    ],
    "name" : "hotpot",
    "height" : 1.85
  }
]"""

//調(diào)用
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
    let result = try? decoder.decode([Hotpot].self, from: data)
    print(result ?? "解析失敗")
}
[SwiftProtocol.Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)]), SwiftProtocol.Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])]

只需要解析模型對應(yīng)的類型傳數(shù)組[Hotpot].self就ok了。

JSON數(shù)據(jù)中有 Optional values

一般情況下后臺可能會傳給我們null值扭弧,如果上面的例子不做修改直接解析會失敗阎姥。處理不當(dāng)可能會crash。

image.png

一般情況下我們把可能為空的值聲明為可選值解決鸽捻。

struct Hotpot: Codable{
    var name: String
    var age: Int?
    var height: Double
    var cat: [Cat]
}

extension Hotpot {
    struct Cat: Codable {
        var name: String
        var age: Int
    }
}

let jsonString = """
[
  {
    "age" : 18,
    "cat" : [
      {
        "age" : 1,
        "name" : "cat"
      },
      {
        "age" : 10,
        "name" : "cat1"
      }
    ],
    "name" : "hotpot",
    "height" : 1.85
  },
  {
    "age" : null,
    "cat" : [
      {
        "age" : 1,
        "name" : "cat"
      },
      {
        "age" : 10,
        "name" : "cat1"
      }
    ],
    "name" : "hotpot",
    "height" : 1.85
  }
]
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
    let result = try? decoder.decode([Hotpot].self, from: data)
    print(result ?? "解析失敗")
}
[SwiftProtocol.Hotpot(name: "hotpot", age: Optional(18), height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)]), SwiftProtocol.Hotpot(name: "hotpot", age: nil, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])]

元組類型

比如我們有一個坐標(biāo)呼巴,后臺服務(wù)器傳給"location": [114.121344, 38.908766],這個時候用數(shù)組接顯然不好維護(hù)御蒲。返回的數(shù)組怎么和模型對應(yīng)上呢? 這個時候就需要自己寫解析實現(xiàn)"init(from decoder: Decoder)"

struct Location: Codable {
    var x: Double
    var y: Double
    
    init(from decoder: Decoder) throws {
        //不解析當(dāng)前的key,也就是解碼時不要key值衣赶。
        var contaioner = try decoder.unkeyedContainer()
        //單方面把值給到x與y
        self.x = try contaioner.decode(Double.self)
        self.y = try contaioner.decode(Double.self)
    }
}

struct RawSeverResponse: Codable {
    var location: Location
}

let jsonString = """
{
    "location": [114.121344, 38.908766]
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(RawSeverResponse.self, from: jsonData!)
print(result)
RawSeverResponse(location: SwiftProtocol.Location(x: 114.121344, y: 38.908766))

繼承

class Hotpot: Codable {
    var name: String?
}

class HotpotCat: Hotpot {
    var age: Int?
}

let jsonString = """
{
    "name": "hotpot",
    "age": 18
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)

按照猜想上面的代碼應(yīng)該agename都能正常解析,看下輸出:

Optional("hotpot")
nil

age沒有解析厚满?那么Codable放在子類上呢府瞄?

class Hotpot {
    var name: String?
}

class HotpotCat: Hotpot, Codable {
    var age: Int?
}

let jsonString = """
{
    "name": "hotpot",
    "age": 18
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)
nil
Optional(18)

name沒有解析。
這個時候就需要我們在派生類中重寫init(from decoder: Decoder)碘箍,如果要編碼還需要encode(to encoder: Encoder)并且需要定義CodingKeys遵馆。

class Hotpot: Codable {
    var name: String?
}

class HotpotCat: Hotpot {
    var age: Int?
    
    private enum CodingKeys: String,CodingKey{
        case name
        case age
     }
    
    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
        try super.encode(to: encoder)
    }
}

let jsonString = """
{
    "name": "hotpot",
    "age": 18
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)
Optional("hotpot")
Optional(18)

為什么派生類不能自動解析?原理后面再分析丰榴。

協(xié)議

如果有協(xié)議的參與下解析會怎么樣呢货邓?

protocol HotpotProtocol: Codable {
    var name: String{ get set }
}

struct Hotpot: HotpotProtocol {
    var name: String
    var age: Int?
}

let jsonString = """
{
    "name": "hotpot",
    "age": 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)
Hotpot(name: "hotpot", age: Optional(18))

可以看到能夠正常解析。

key值不同

對于一些不符合規(guī)范的字段四濒,我們一般是會給別名來解析逻恐,那么在Codable中怎么實現(xiàn)呢?
比如后臺返回了這樣的數(shù)據(jù)峻黍,正常情況下應(yīng)該返回一個數(shù)組和對應(yīng)的type。

let jsonString = """
{
  "01Type" : "hotpot",
  "item.1" : "mouse",
  "item.2" : "dog",
  "item.3" : "hotpot",
  "item.0" : "cat"
}
"""
//如果不需要編碼拨匆,直接遵循 Decodable 就好了
struct Hotpot: Decodable {
    var type: String?
    let elements: [String]
    enum CodingKeys: String, CaseIterable, CodingKey {
        case type = "01Type"
        case item0 = "item.0"
        case item1 = "item.1"
        case item2 = "item.2"
        case item3 = "item.3"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.type = try container.decode(String.self, forKey: .type)
        var element: [String]  = []
        //方式一:遍歷 container
        for item in container.allKeys {
            switch item {
                case .item0,.item1,.item2,.item3:
                    element.append(try container.decode(String.self, forKey: item))
                default:
                    continue
            }

        }
        //方式二:遍歷CodingKeys 和 contains比較裝入集合
//        for item in CodingKeys.allCases {
//            guard container.contains(item) else {
//                continue
//            }
//            if item != .type {
//                element.append(try container.decode(String.self, forKey: item))
//            }
//        }
        self.elements = element
    }
}

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)

print(result)
Hotpot(type: Optional("hotpot"), elements: ["cat", "mouse", "dog", "hotpot"])

源碼解析

Decodable

public protocol Decodable {
    init(from decoder: Decoder) throws
}

對應(yīng)的Decoder提供具體解碼

public protocol Decoder {
    var codingPath: [CodingKey] { get }
    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
}

Encodable

public protocol Encodable {
    func encode(to encoder: Encoder) throws
}

對應(yīng)的Encoder提供具體編碼

public protocol Encoder {
    var codingPath: [CodingKey] { get }
    var userInfo: [CodingUserInfoKey : Any] { get }
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey
    func unkeyedContainer() -> UnkeyedEncodingContainer
    func singleValueContainer() -> SingleValueEncodingContainer
}

JSONDecoder

open class JSONDecoder {
    public enum DateDecodingStrategy {
        /// Defer to `Date` for decoding. This is the default strategy.
        case deferredToDate
        /// 距離 1970.01.01的秒數(shù)
        case secondsSince1970
        /// 距離 1970.01.01的毫秒數(shù)
        case millisecondsSince1970
       ///日期編碼格式
        @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
        case iso8601
        ///后臺自定義格式姆涩,這個時候可以創(chuàng)建自定義DataFormatter來解析
        case formatted(DateFormatter)
       ///自定義格式,提供一個閉包表達(dá)式
        case custom((Decoder) throws -> Date)
    }

    /// 二進(jìn)制數(shù)據(jù)解碼策略
    public enum DataDecodingStrategy {
        /// 默認(rèn)
        case deferredToData
        /// base64
        case base64
        /// 自定義
        case custom((Decoder) throws -> Data)
    }

    /// 不合法浮點數(shù)編碼策略
    public enum NonConformingFloatDecodingStrategy {
        /// Throw upon encountering non-conforming values. This is the default strategy.
        case `throw`
        /// Decode the values from the given representation strings.
        case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
    }

    /// key值編碼策略
    public enum KeyDecodingStrategy {
        /// Use the keys specified by each type. This is the default strategy.
        case useDefaultKeys
        //指定去掉中間下劃線惭每,變成駝峰命名
        case convertFromSnakeCase
        //自定義
        case custom(([CodingKey]) -> CodingKey)
    }

    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]
    /// Initializes `self` with default strategies.
   ///默認(rèn)init方法
    public init()
    /// Decodes a top-level value of the given type from the given JSON representation.
    /// - parameter type: The type of the value to decode.
    /// - parameter data: The data to decode from.
    /// - returns: A value of the requested type.
    /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
    /// - throws: An error if any value throws an error during decoding.
    open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}

DateDecodingStrategy

以何種策略解析日期格式骨饿。

let jsonString = """
{
  "name" : "hotpot",
  "age" : 18,
  "date" : 1610390703
}
"""

struct Hotpot: Codable {
    var name: String
    var age: Double
    var date: Date
}

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)

deferredToDate

  "date" : 1610390703
decoder.dateDecodingStrategy = .deferredToDate
Hotpot(name: "hotpot", age: 18.0, date: 2052-01-12 18:45:03 +0000)

使用默認(rèn)格式無法正確推導(dǎo)出時間。
secondsSince1970

decoder.dateDecodingStrategy = .secondsSince1970
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)

millisecondsSince1970

  "date" : 1610390703000
decoder.dateDecodingStrategy = .millisecondsSince1970
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)

iso8601

  "date" : "2021-01-11T19:20:20Z"
decoder.dateDecodingStrategy = .iso8601
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 19:20:20 +0000)

formatted

  "date" : "2021/01/11 19:20:20"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 11:20:20 +0000)

custom
custom相對比較靈活台腥,可以做一些錯誤處理以及格式不固定的情況下使用宏赘。

  "date" : "1610390703"
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
    let container = try decoder.singleValueContainer()
    let dateStr = try container.decode(String.self)
    return Date(timeIntervalSince1970: Double(dateStr)!)
})
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)

func decode源碼:

    open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {//泛型函數(shù),同時當(dāng)前泛型約束為遵循了Decodable的協(xié)議
        let topLevel: Any
        do {
            //JSONSerialization將二進(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))
        }
        // __JSONDecoder 傳入序列化的json  和  self.options 編碼策略創(chuàng)建decoder對象
        let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
        //unbox 拆 topLevel 數(shù)據(jù) 返回解碼后的value
        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.這里是一個泛型函數(shù)黎侈,傳入的參數(shù)T 要求遵守 Decodable 協(xié)議;
2.調(diào)用 JSONSerializationg 對當(dāng)前 data 進(jìn)行序列話為json數(shù)據(jù)察署;
3.通過__JSONDecoder傳入json數(shù)據(jù)和編碼策略創(chuàng)建decoder對象;
4.unbox解碼json串峻汉,返回value贴汪。
__JSONDecoder私有類源碼(初始化方法):

   init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
        //內(nèi)部類 _JSONDecodingStorage
        self.storage = _JSONDecodingStorage()
        //存放要解碼的數(shù)據(jù)
        self.storage.push(container: container)
        self.codingPath = codingPath
        self.options = options
    }

_JSONDecodingStorage本身用數(shù)組存放數(shù)據(jù)脐往,是一個容器

private struct _JSONDecodingStorage {
    // MARK: Properties

    /// The container stack.
    /// Elements may be any one of the JSON types (NSNull, NSNumber, String, Array, [String : Any]).
    private(set) var containers: [Any] = []

    // MARK: - Initialization

    /// Initializes `self` with no containers.
    init() {}

    // MARK: - Modifying the Stack

    var count: Int {
        return self.containers.count
    }

    var topContainer: Any {
        precondition(!self.containers.isEmpty, "Empty container stack.")
        return self.containers.last!
    }

    mutating func push(container: __owned Any) {
        self.containers.append(container)
    }

    mutating func popContainer() {
        precondition(!self.containers.isEmpty, "Empty container stack.")
        self.containers.removeLast()
    }
}

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

分析下日期格式的處理

image.png

這也就是我們設(shè)置不同的日期處理策略能夠正常解析的原因扳埂。不同的策略走不同的分支解析业簿。
_JSONStringDictionaryDecodableMarker

#if arch(i386) || arch(arm)
internal protocol _JSONStringDictionaryDecodableMarker {
    static var elementType: Decodable.Type { get }
}
#else
private protocol _JSONStringDictionaryDecodableMarker {
    static var elementType: Decodable.Type { get }
}
#endif

extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, Value: Decodable {
    static var elementType: Decodable.Type { return Value.self }
}
  • 字典擴展遵循了這個協(xié)議,限制條件:Key == String, Value: Decodable阳懂。
    _JSONStringDictionaryDecodableMarkerunbox:
    image.png

    是一個遞歸調(diào)用梅尤。unbox_就是最開始的區(qū)分類型(DateData……)的方法岩调。

看一個例子:

struct Hotpot: Codable {
    var name: String
    var age: Double
}

let jsonString = """
{
  "name" : "hotpot",
  "age" : 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)

對應(yīng)的SIL代碼:

image.png

  • 解碼對應(yīng)的key值通過CodingKeys去找巷燥,decodable約束的。
  • decode方法誊辉。
  • encode方法矾湃。

init方法源碼中是:

   return try type.init(from: self)

傳入的decoderself,這里的selfJSONDecoder堕澄。
init(from SIL的實現(xiàn):

image.png

查看_ JSONDecoder源碼確實實現(xiàn)了Decoder協(xié)議
image.png

sil中調(diào)用的是_JSONDecoder實現(xiàn)的container方法:
image.png

這里返回的是KeyedDecodingContainer(container)對象邀跃。
KeyedDecodingContainer內(nèi)容:
image.png

可以看到有對應(yīng)每種類型的decode方法,意味著會根據(jù)類型匹配對應(yīng)的方法解碼蛙紫。這么多方法蘋果其實是用內(nèi)部的一個工具生成的拍屑。具體是用源碼中的Codable.swift.gyb文件。通過Codable.swift.gyb生成Codable.swift源文件坑傅。
Codable.swift.gyb文件內(nèi)容:
image.png

集合中放了可編解碼類型僵驰,%%代表語句的開始和結(jié)束。通過python控制的唁毒。相當(dāng)于模板文件蒜茴。
image.png

自己實現(xiàn)下init方法:

struct Hotpot: Codable {
    var name: String
    var age: Double
    
    //_JSONDecoder,返回container
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Double.self, forKey: .age)
    }
}

可以看到對應(yīng)的CodingKeysdecode正是系統(tǒng)生成調(diào)用的方法,CodingKeys系統(tǒng)幫我們生成了浆西,這也就是我們可以在CodingKeys中操作key不一致的原因粉私。

那么如果繼承自OC的類呢?
HPOCModel是一個OC類:

@interface HPOCModel : NSObject

@property (nonatomic, assign) double height;

@end

@implementation HPOCModel

@end
class Hotpot:HPOCModel, Decodable {
    var name: String
    var age: Double
    
     private enum CodingKeys: String,CodingKey{
        case name
        case age
        case height
     }

    //_JSONDecoder,返回container
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Double.self, forKey: .age)
        super.init()
        self.height = try container.decode(Double.self, forKey: .height)
    }
}

let jsonString = """
{
  "name" : "hotpot",
  "age" : 18,
  "height":1.85
}
"""

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()

let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result.height)
1.85

這個時候init方法就要我們自己去實現(xiàn)了近零。

整個流程:


image.png

JSONEncoder


class Hotpot: Codable {
    var name: String?
    var age: Int?
}

class Cat: Hotpot {
    var height: Double?
}

let cat = Cat()
cat.name = "cat"
cat.age = 18
cat.height = 1.85

let enCoder = JSONEncoder()
let jsonData = try enCoder.encode(cat)
let jsonStr = String(data: jsonData, encoding: .utf8)
print(jsonStr)
Optional("{\"name\":\"cat\",\"age\":18}")

源碼分析:
encode方法:

    open func encode<T : Encodable>(_ value: T) throws -> Data {
        let encoder = _JSONEncoder(options: self.options)
        //先box包裝成string
        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)
        //返回data
        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))
        }
    }
  • 在這里真正處理數(shù)據(jù)的是_JSONEncoder诺核;
  • box根據(jù)不同數(shù)據(jù)類型,把value包裝成對應(yīng)的數(shù)據(jù)類型久信;
  • JSONSerialization來返回data數(shù)據(jù)窖杀。

_JSONEncoder:

   //實現(xiàn)Encoder的方法
    public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
        // If an existing keyed container was already requested, return that one.
        let topContainer: NSMutableDictionary
        if self.canEncodeNewValue {
            // We haven't yet pushed a container at this level; do so here.
            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)
       //返回KeyedEncodingContainer,最終KeyedEncodingContainer encode根據(jù)數(shù)據(jù)類型編碼數(shù)據(jù)
        return KeyedEncodingContainer(container)
    }
  • _JSONEncoder遵循Encoder協(xié)議實現(xiàn)了container方法裙士,最終KeyedEncodingContainer提供對應(yīng)數(shù)據(jù)類型encode方法編碼數(shù)據(jù)入客。

box_:

image.png

我們自定義數(shù)據(jù)類型會走到value.encode,參數(shù)self就是_JSONEcoder。正好的解碼過程相反痊项。
最終都會都到value is _JSONStringDictionaryEncodableMarker
_JSONStringDictionaryEncodableMarker:
image.png

在最開始的例子中锅风,Cat中的height并沒有被編碼出來。
先看下繼承和協(xié)議遵循關(guān)系:

image.png

那么Hotpot遵循了Codable協(xié)議鞍泉,編譯器默認(rèn)生成了encode方法皱埠,在調(diào)用過程中由于Cat中沒有對應(yīng)encode方法,所以會去父類中找咖驮。所以編碼結(jié)果中沒有height边器。
SIL驗證下:

class Hotpot : Decodable & Encodable {
  @_hasStorage @_hasInitialValue var name: String? { get set }
  @_hasStorage @_hasInitialValue var age: Int? { get set }
  @objc deinit
  init()
  enum CodingKeys : CodingKey {
    case name
    case age
    @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Hotpot.CodingKeys, _ b: Hotpot.CodingKeys) -> Bool
    var hashValue: Int { get }
    func hash(into hasher: inout Hasher)
    var stringValue: String { get }
    init?(stringValue: String)
    var intValue: Int? { get }
    init?(intValue: Int)
  }
  required init(from decoder: Decoder) throws
  func encode(to encoder: Encoder) throws
}

@_inheritsConvenienceInitializers class Cat : Hotpot {
  @_hasStorage @_hasInitialValue var height: Double? { get set }
  @objc deinit
  override init()
  required init(from decoder: Decoder) throws
}

Hotpot中有initencode方法。而在Cat中編譯器覆蓋了init(from decoder: Decoder)方法托修,而沒有覆蓋encode忘巧。這也就解釋了解碼能夠無縫對接,編碼不行的原因睦刃。

那么我們需要在子類中重寫encode方法:

class Hotpot: Codable {
    var name: String?
    var age: Int?
}

class Cat: Hotpot {
    var height: Double?
    
    enum CodingKeys: String,CodingKey {
        case height
    }
    
    override func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(height, forKey: .height)
       try super.encode(to: encoder)
    }
}

let cat = Cat()
cat.name = "cat"
cat.age = 18
cat.height = 1.8

let enCoder = JSONEncoder()
let jsonData = try enCoder.encode(cat)
let jsonStr = String(data: jsonData, encoding: .utf8)
Optional("{\"name\":\"cat\",\"age\":18,\"height\":1.8}")

當(dāng)然這里要注意數(shù)據(jù)的精度問題砚嘴,前面用法里面已經(jīng)說過了。

有一個小的點try super.encode(to: encoder)如果用自己的編碼器呢涩拙?

try super.encode(to: container.superEncoder())
Optional("{\"super\":{\"name\":\"cat\",\"age\":18},\"height\":1.8}")

編碼值中多了super际长。所以這里最好就傳當(dāng)前類的encoder。否則多次編解碼會錯誤解析不到數(shù)據(jù)兴泥。

對于上面編碼后的值工育,再解碼看看:

let cat2 = try JSONDecoder().decode(Cat.self, from: jsonData)
Optional("{\"name\":\"cat\",\"age\":18,\"height\":1.8}")
SwiftProtocol.Cat
(lldb) po cat2.name
? Optional<String>
  - some : "cat"

(lldb) po cat2.age
? Optional<Int>
  - some : 18

(lldb) po cat2.height
nil

可以看到height沒有解析出來。這和前面的結(jié)論一樣搓彻,看下SIL:

image.png

在最后確實調(diào)用了Hotpotdecode方法如绸,所以父類中的數(shù)據(jù)解析沒有問題,但是heightSIL中只有創(chuàng)建沒有賦值旭贬。沒有創(chuàng)建Cat自己本身的container怔接。再看下和Hotpot的區(qū)別:
image.png

可以看到區(qū)別是僅僅Cat中根本沒有創(chuàng)建Container,僅是調(diào)用了父類的decode方法稀轨,這也是為什么沒有解析出來自己的屬性蜕提。
(Swift源碼中繼承后在vscode中調(diào)試報錯,只能編譯一份Xcode源碼再調(diào)試了靶端,后面再補充吧).
image.png

源碼調(diào)試待補充
我們自己實現(xiàn)catinit方法看下sil代碼:

class Hotpot:Codable {
    var name: String?
    var age: Int?
}

class Cat: Hotpot {
    var height: Double?
    enum CodingKeys: String, CodingKey {
        case height
    }
    required init(from decoder: Decoder) throws {
        var container = try decoder.container(keyedBy: CodingKeys.self)
        self.height = try container.decode(Double.self, forKey: .height)
        try! super.init(from: decoder)
    }
}


let jsonString = "{\"name\" : \"hotpot\",\"age\" : 18,\"height\":1.8}"

let cat2 = try JSONDecoder().decode(Cat.self, from: jsonString.data(using: .utf8)!)
print(cat2)
SwiftProtocol.Cat
(lldb) po cat2.age
? Optional<Int>
  - some : 18

(lldb) po cat2.height
? Optional<Double>
  - some : 1.8

這樣就能正常解析了,注釋掉不必要的代碼看下SIL

image.png

  • Cat創(chuàng)建了自己的container
  • bb1中對height進(jìn)行了解析賦值凛膏;
  • bb2分支中調(diào)用了Hotpotinit方法杨名。

編解碼多態(tài)中的應(yīng)用

protocol Hotpot: Codable {
    var age: String { get set }
    var name: String { get set }
}

struct Cat: Hotpot {
    var age: String
    var name: String
}

struct Dog: Hotpot {
    var age: String
    var name: String
}

struct Animal: Codable{
    var animals: [Hotpot]
    var desc: String
    
    enum CodingKeys: String, CodingKey {
        case animals
        case desc
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(animals, forKey: .animals)
        try container.encode(desc, forKey: .desc)
    }
}

上面的例子中,Animalanimals存儲的是遵循Hotpot協(xié)議的屬性:

image.png

看到直接報錯猖毫,有自定義類型就需要我們自己實現(xiàn)initdecode方法了台谍。并且animals不能解析,需要各自的類型(Cat吁断、Dog)自己去實現(xiàn)相應(yīng)的initdecode方法趁蕊。這在類型比較少的時候還能接受坞生,類型多了的情況下呢?
那么可以用一個中間層HotpotBox去專門做解析:

protocol Hotpot {
    var age: Int { get set }
    var name: String { get set }
}

struct HotpotBox: Hotpot, Codable {
    var age: Int
    var name: String
    
    init(_ hotpot: Hotpot) {
        self.age = hotpot.age
        self.name = hotpot.name
    }
}

struct Cat: Hotpot {
    var age: Int
    var name: String
}

struct Dog: Hotpot {
    var age: Int
    var name: String
}

struct Animal: Codable{
    var animals: [HotpotBox]
    var desc: String
}

調(diào)用

let hotpots: [Hotpot] = [Cat(age: 18, name: "cat"),Dog(age: 28, name: "dog")]
//對hotpots數(shù)組中的集合執(zhí)行HotpotBox.init是己,也就是用hotpots初始化animals
let animal = Animal(animals: hotpots.map(HotpotBox.init), desc: "Animal")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(animal)

if let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

輸出

{
  "animals" : [
    {
      "age" : 18,
      "name" : "cat"
    },
    {
      "age" : 28,
      "name" : "dog"
    }
  ],
  "desc" : "Animal"
}

decode一下:

let animal1: Animal = try JSONDecoder().decode(Animal.self, from: jsonData)
print(animal1)

輸出:

Animal(animals: [SwiftProtocol.HotpotBox(age: 18, name: "cat"), SwiftProtocol.HotpotBox(age: 28, name: "dog")], desc: "Animal")

可以看到這里輸出的是HotpotBox,如果要還原當(dāng)前的類型信息呢任柜?
1.可以寫一個對應(yīng)的unBox來還原數(shù)據(jù):

struct Cat: Hotpot {
    var age: Int
    var name: String
    static func unBox(_ value: HotpotBox) -> Cat {
        var cat = Cat(age: value.age, name: value.name)
        return cat
    }
}
[SwiftProtocol.Cat(age: 18, name: "cat"), SwiftProtocol.Cat(age: 28, name: "dog")]

2.編碼過程中將類型信息編碼進(jìn)去卒废。

enum HotpotType: String, Codable {
    case cat
    case dog
    
    var metadata: Hotpot.Type {
        switch self {
            case .cat:
                return Cat.self
            case .dog:
                return Dog.self
        }
    }
}

protocol Hotpot: Codable {
    static var type: HotpotType { get }//計算屬性,編解碼過程中不會被編碼進(jìn)去宙地。
    var age: Int { get set }
    var name: String { get set }
}


struct HotpotBox: Codable {
    var hotpot: Hotpot
    
    init(_ hotpot: Hotpot) {
        self.hotpot = hotpot
    }
    
    private enum CodingKeys: String, CodingKey {
        case type
        case hotpot
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(HotpotType.self, forKey: .type)
        self.hotpot = try type.metadata.init(from: container.superDecoder(forKey: .hotpot))
    }
    
    func encode(to encoder: Encoder) throws {
        var container = try encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type(of: hotpot).type, forKey: .type)
        try hotpot.encode(to: container.superEncoder(forKey: .hotpot))
    }
}

struct Cat: Hotpot {
    static var type: HotpotType = .cat
    var age: Int
    var name: String
}

struct Dog: Hotpot {
    static var type: HotpotType {
        .dog
    }
    var age: Int
    var name: String
}

struct Animal: Codable {
    var animals: [HotpotBox]
    var desc: String
}


let hotpots: [Hotpot] = [Cat(age: 18, name: "cat"),Dog(age: 28, name: "dog")]
//對hotpots數(shù)組中的集合執(zhí)行HotpotBox.init摔认,也就是用hotpots初始化animals
let animal = Animal(animals: hotpots.map(HotpotBox.init), desc: "Animal")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(animal)

if let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

let animal1: Animal = try JSONDecoder().decode(Animal.self, from: jsonData)
print(animal1)

輸出

{
  "animals" : [
    {
      "type" : "cat",
      "hotpot" : {
        "age" : 18,
        "name" : "cat"
      }
    },
    {
      "type" : "dog",
      "hotpot" : {
        "age" : 28,
        "name" : "dog"
      }
    }
  ],
  "desc" : "Animal"
}
Animal(animals: [SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Cat(age: 18, name: "cat")), SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Dog(age: 28, name: "dog"))], desc: "Animal")

這個時候看到有無用信息,再改造下:

self.hotpot = try type.metadata.init(from: container.superDecoder(forKey: .hotpot))

改為

 self.hotpot = try type.metadata.init(from: decoder)
{
  "animals" : [
    {
      "type" : "cat",
      "age" : 18,
      "name" : "cat"
    },
    {
      "type" : "dog",
      "age" : 28,
      "name" : "dog"
    }
  ],
  "desc" : "Animal"
}
Animal(animals: [SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Cat(age: 18, name: "cat")), SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Dog(age: 28, name: "dog"))], desc: "Animal")

如果不想要type

    func encode(to encoder: Encoder) throws {
        var container = try encoder.container(keyedBy: CodingKeys.self)
//        try container.encode(type(of: hotpot).type, forKey: .type)
        try hotpot.encode(to: encoder)
    }

去掉就好了宅粥,不過在解碼的時候就沒有type相關(guān)信息了参袱。需要根據(jù)需求靈活處理

{
  "animals" : [
    {
      "age" : 18,
      "name" : "cat"
    },
    {
      "age" : 28,
      "name" : "dog"
    }
  ],
  "desc" : "Animal"
}

另外一種方式

protocol Meta: Codable {
    associatedtype Element
    
    static func metatype(for typeString: String) -> Self
    var type: Decodable.Type { get }
}

struct MetaObject<M: Meta>: Codable {
    let object: M.Element
    
    init(_ object: M.Element) {
        self.object = object
    }
    
    enum CodingKeys: String, CodingKey {
        case metatype
        case object
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let typeStr = try container.decode(String.self, forKey: .metatype)
        let metatype = M.metatype(for: typeStr)
        
        let superDecoder = try container.superDecoder(forKey: .object)
        let obj = try metatype.type.init(from: superDecoder)
        guard let element = obj as? M.Element else {
            fatalError()
        }
        self.object = element
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        let typeStr = String(describing: type(of: object))
        try container.encode(typeStr, forKey: .metatype)
        
        let superEncoder = container.superEncoder(forKey: .object)
        let encodable = object as? Encodable
        try encodable?.encode(to: superEncoder)
    }
}


enum HotpotType: String, Meta {
    typealias Element = Hotpot
    case cat = "Cat"
    case dog = "Dog"

    static func metatype(for typeString: String) -> HotpotType {
        guard let metatype = self.init(rawValue: typeString) else {
            fatalError()
        }
        return metatype
    }

    var type: Decodable.Type {
        switch self {
            case .cat:
                return Cat.self
            case .dog:
                return Dog.self
        }
    }
}

class Hotpot: Codable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Cat: Hotpot {
    var height: Double
    init(name: String, age: Int, height: Double) {
        self.height = height
        super.init(name: name, age: age)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        height = try container.decode(Double.self, forKey: .height)
        try super.init(from: decoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(height, forKey: .height)
        try super.encode(to: encoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case height
    }
}

class Dog: Hotpot {
    var weight: Double
    
    init(name: String, age: Int, weight: Double) {
        self.weight = weight
        super.init(name: name, age: age)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        weight = try container.decode(Double.self, forKey: .weight)
    
        let superDecoder = try container.superDecoder()
        try super.init(from: superDecoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(weight, forKey: .weight)
        
        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case weight
    }
}


let hotpot: Hotpot = Cat(name: "cat", age: 18, height: 1.8)
let jsonData = try JSONEncoder().encode(MetaObject<HotpotType>(hotpot))
if let str = String(data: jsonData, encoding: .utf8) {
    print(str)
}

let decode: MetaObject<HotpotType> = try JSONDecoder().decode(MetaObject<HotpotType>.self, from: jsonData)

print(decode)
{"metatype":"Cat","object":{"name":"cat","age":18,"height":1.8}}

MetaObject<HotpotType>(object: SwiftProtocol.Cat)
  • 中間層
  • 記錄metadata
  • Type

在編解碼過程中遇到多態(tài)的方式,可以通過中間層去解決處理秽梅。

SwiftUI中Codable

當(dāng)我們在SwiftUI中定義如下代碼會直接報錯(ObservableObjectPublished可以實現(xiàn)組件數(shù)據(jù)一致性抹蚀,而且可以自動更新):

class Hotpot: ObservableObject, Codable {
    @Published var name = "cat"
}

image.png

這實際上是Swift的一項基本原則,我們不能說var names: Set风纠,因為Set是通用類型,必須創(chuàng)建Set<String>的實例况鸣。Published也是同理,不能自己創(chuàng)建Published的實例竹观,而是創(chuàng)建一個Published<String>的實例镐捧。
這時候就需要我們自己編寫編解碼操作了:

class Hotpot: ObservableObject, Codable {
    @Published var name = "cat"
    enum CodingKeys: CodingKey {
        case name
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
    }
}

比較

Codable:繼承多態(tài)模式下需要自己編寫編解碼。
HandyJSON:內(nèi)存賦值的方式進(jìn)行編解碼操作臭增,依賴metadata懂酱。如果metadata變化后會出現(xiàn)問題,遷移成本比較高誊抛。
SwiftyJSON:需要使用下標(biāo)的方式取值列牺。
ObjectMapper:需要手動對每一個屬性提供映射關(guān)系。
如果項目中數(shù)據(jù)模型繼承和多態(tài)比較少建議直接用Codable拗窃,否則就用HandyJSON吧瞎领。相對來說Codable效率更高。
具體的對比以后再補充吧随夸。

參考:
https://zhuanlan.zhihu.com/p/50043306
http://www.reibang.com/p/f4b3dce8bd6f
http://www.reibang.com/p/1f194f09599a
http://www.reibang.com/p/bdd9c012df15
http://www.reibang.com/p/6db40c4c0ff9
http://www.reibang.com/p/5dab5664a621?utm_campaign
http://www.reibang.com/p/bf56a74323fa
http://www.reibang.com/p/0088b62f698a
enum解析
解析框架對比
Double精度問題
NSDecimalNumber

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末九默,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宾毒,更是在濱河造成了極大的恐慌驼修,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異乙各,居然都是意外死亡墨礁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門耳峦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恩静,“玉大人,你說我怎么就攤上這事妇萄⊥善螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵冠句,是天一觀的道長轻掩。 經(jīng)常有香客問我,道長懦底,這世上最難降的妖魔是什么唇牧? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮聚唐,結(jié)果婚禮上丐重,老公的妹妹穿的比我還像新娘。我一直安慰自己杆查,他們只是感情好扮惦,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亲桦,像睡著了一般崖蜜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上客峭,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天豫领,我揣著相機與錄音,去河邊找鬼舔琅。 笑死等恐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的备蚓。 我是一名探鬼主播课蔬,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼郊尝!你這毒婦竟也來了购笆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虚循,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體横缔,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡铺遂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茎刚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片襟锐。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膛锭,靈堂內(nèi)的尸體忽然破棺而出粮坞,到底是詐尸還是另有隱情,我是刑警寧澤初狰,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布莫杈,位于F島的核電站,受9級特大地震影響奢入,放射性物質(zhì)發(fā)生泄漏筝闹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一腥光、第九天 我趴在偏房一處隱蔽的房頂上張望关顷。 院中可真熱鬧,春花似錦武福、人聲如沸议双。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽平痰。三九已至,卻和暖如春界睁,著一層夾襖步出監(jiān)牢的瞬間觉增,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工翻斟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逾礁,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓访惜,卻偏偏與公主長得像嘹履,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子债热,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容