Swift-Coadable源碼解析

這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰json

Swift-Coadable源碼解析

Codable常見用法

嵌套的模型

struct LGTeacher: Codable{
    var name: String
    var className: String
    var courceCycle: Int
    var personInfo: PersonInfo
}

extension LGTeacher {
    struct PersonInfo: Codable {
        var age: Int
        var height: Double
    }
}

let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "personInfo": { "age": 18, "height": 1.85 } } """

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

JSON數據中包含數組

struct LGTeacher: Codable{
    var name: String
    var className: String
    var courceCycle: Int
    var personInfo: [PersonInfo]
}

extension LGTeacher {
    struct PersonInfo: Codable {
        var age: Int
        var height: Double
    }
}

let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "personInfo": [ { "age": 18, "height": 1.85 },{ "age": 20, "height": 1.75 } ] } """

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

JSON數據是一組數組集合

struct LGTeacher: Codable{
    var name: String
    var className: String
    var courceCycle: Int
}


let jsonString = """ [ { "name": "Kody", "className": "Swift", "courceCycle": 12 },{ "name": "Cat", "className": "強化班", "courceCycle": 15 },{ "name": "Hank", "className": "逆向班", "courceCycle": 22 },{ "name": "Cooci", "className": "大師班", "courceCycle": 22 } ] """

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
    let result = try? decoder.decode([LGTeacher].self, from: data)
    print(result ?? "解析失敗")
}
複製代碼

JSON數據中有 Optional values

**swift

let jsonString = """ [ { "name": "Kody", "className": "Swift", "courceCycle": 12 },{ "name": "Cat", "className": "強化班", "courceCycle": 15 },{ "name": "Hank", "className": null, "courceCycle": 22 },{ "name": "Cooci", "className": "大師班", "courceCycle": 22 } ] """

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
    let result = try? decoder.decode([LGTeacher].self, from: data)
    print(result ?? "解析失敗")
}
複製代碼

image.png
image.png

元組類型

好比咱們有一個座標,location : [20, 10],當咱們在使用Codable進行解析的過程當中,咱們須要進行以下操做:數組

struct Location: Codable {
    var x: Double
    var y: Double
    
    init(from decoder: Decoder) throws{
        var contaioner = try decoder.unkeyedContainer()
        
        self.x = try contaioner.decode(Double.self)
        self.y = try contaioner.decode(Double.self)
    }
}

struct RawSeverResponse: Codable{
    var location: Location
    
}

let jsonString = """ { "location": [20, 10] } """

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(RawSeverResponse.self, from: jsonData!)
print(result.location.x)
複製代碼

image.png
image.png

嵌套的數據模型

繼承

class LGTeacher: Codable {
    var name: String?
}

class LGPartTimeTeacher: LGTeacher {
    var partTime: Int?
}


let jsonString = """ { "name": "Kody", "partTime": 20 } """

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(LGPartTimeTeacher.self, from: jsonData!)
print(result.name)
複製代碼
protocol LGTeacher {
    var name: String{ get set }
}
//
struct LGPartTimeTeacher: LGTeacher, Codable {
    var name: String
    var partTime: Int?
}
//
//
let jsonString = """ { "name": "Kody", "partTime": 20 } """
//
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(LGPartTimeTeacher.self, from: jsonData!)
print(result)
複製代碼

不方便的數組類型

struct LGPerson: Decodable{
    
    let elements: [String]
    
    enum CodingKeys: String, CaseIterable, CodingKey {
        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)
        
        var element: [String]  = []
        
        for item in CodingKeys.allCases{
            guard container.contains(item) else { break }
            
            element.append(try container.decode(String.self, forKey: item))
        }
        
        self.elements = element
    }
}
//
//
let jsonString = """ { "item.3": "Kody", "item.0": "Hank", "item.2": "Cooci", "item.1": "Cat" } """

let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(LGPerson.self, from: jsonData!)
print(result)
複製代碼

image.png
image.png

Codable源碼解析

解碼處理流程


咱們先來看一下 Codable 究竟是什麼?
image.png
image.png
image.png

這裏咱們使用一個簡單的案例來看一下:markdown

struct LGTeacher: Codable {
    var name: String
    var className: String
    var courceCycle: Int
}

let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10 } """

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

當前咱們建立一個解碼的對象,而後調用 decode 方法將咱們的 json 字符串解析給咱們的模型 LGTeacher 。這裏咱們須要探究的是它到底是如何工做的?

第一:咱們先來看一下 JSONDecoder 建立出來的對象app

public enum DateDecodingStrategy {

        /// Defer to `Date` for decoding. This is the default strategy.
        case deferredToDate
    
    	/// 表明距離 1970.01.01 的秒數
        /// Decode the `Date` as a UNIX timestamp from a JSON number.
        case secondsSince1970

    	/// 表明距離 1970.1.1 的毫秒數
        /// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
        case millisecondsSince1970

        /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
        @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
        case iso8601
		
    	/// 後臺自定義的格式,這個時候咱們能夠本身建立 DateFormatter,來解析
        /// Decode the `Date` as a string parsed by the given formatter.
        case formatted(DateFormatter)
       
    	/// 自定義格式
        /// Decode the `Date` as a custom value decoded by the given closure.
        case custom((Decoder) throws -> Date)
    }
複製代碼

這裏咱們來看一下實際的使用場景:ide

struct LGTeacher: Codable {
    var name: String
    var className: String
    var courceCycle: Int
    var date: Date
}

let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10 "date": "1969-09-26T12:00:00Z" } """
複製代碼

若是咱們直接使用默認的解析策略,那麼這裏代碼運行以後,就會出現解析失敗函數

decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.iso8601
複製代碼
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": 1609183207 } """
複製代碼
decoder.dateDecodingStrategy = .secondsSince1970
複製代碼
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": 1609183207000 } """

decoder.dateDecodingStrategy = .millisecondsSince1970
複製代碼
let jsonString = """ { "name": "Kody", "className": "Swift", "courceCycle": 10, "date": "2020/12/28 19:20:00" } """

這種後臺自定義的格式,須要咱們建立一個DateFormatter

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
複製代碼

image.png
以上就是 JSONDecoder 的主要內容,定義了編碼的策略,可讓咱們根據不一樣的場景來進行選擇。

接下來咱們實際看一下當前是如何 decode 的
image.pngoop

  • 這裏是一個泛型函數,傳入的參數 T 要求遵照 Decodable 協議。
  • 調用 JSONSerializationg 對當前 data 進行序列話的操做
  • 調用內部類 _JSONDecoder 建立一個對象,而後調用 unBox 解碼


這裏咱們關注第三步和第四步,首先從第三步提及:這裏返回了一個Decoder的對象。
image.png

其中 storage 的實現:
image.png
回到咱們的第四步,unBox 就是開始拆盒子
image.png
能夠看到這裏就是匹配對應的類型,而後執行條件分支

回到咱們當前的main.swift ,這個時候咱們並無實現任何的方法
image.png
那麼這裏到底發生了什麼?咱們藉助咱們的老朋友SIL來看一下:
image.png
也就意味着,當前編譯器自動生成了一個默認的init(from:) 實現。咱們這裏來一塊兒閱讀一下,發生了什麼事情:
image.png

那查找的協議方法是什麼?咱們回到剛纔的_JSONDecoder 的實現:
image.png
image.png

咱們來比對一下:
image.png
image.png
這裏就是再調用當前的找到的container 方法的實現;而這個方法在哪裏實現了?是否是就是在_JSONDecoder裏面,咱們下一個斷點看一下
image.png
image.png

接下來就有個問題了,是如何遍歷咱們當前的 Key 值,來進行key-value 的賦值操做?首先咱們先看到 SIL 文件
image.png
首先在內存當中建立了枚舉類型name,而後調用decode()方法,這個時候咱們在回到咱們的

找到KeyedDecodingContainer的具體實現:
image.png
此時的Container是什麼?是否是就是
image.png
image.png
這裏的Decode方法是有_box調用的,咱們回過頭來在來找一下
image.png
基本上到這裏咱們就明白了,實際上是在調用_JSONKeyedDecodingContainerdecode方法,這裏咱們直接經過斷點的方式來肯定一下:
image.png
image.png
post

編碼流程處理

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

Codable坑點分析


上面的案例中,咱們在課程的剛開始講過一個案例,那就是繼承。那麼若是咱們把案例修改一下:測試

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

class LGTeacher: LGPerson {
    var subjectName: String?

}

class LGPartTimeTeacher: LGPerson{
    var partTime: Double?
}

let t = LGTeacher()
t.age = 10
t.name = "Kody"
t.subjectName = "Swift"

let encoder = JSONEncoder()
let encoderData = try encoder.encode(t)
print(String(data: encoderData, encoding: .utf8))
複製代碼

咱們先來看一下上面這個案例可否正常編碼成功
image.png
能夠看到當前僅僅能正常編碼成功咱們的 age 和 name ,可是咱們 subjectName 無法正常編碼,咱們來 debug 看一下當前是爲何?
image.png
image.png
咱們當前的 type 是 LGTeacher 並無遵照 if 的各類分支,因此當前代碼就執行到一下分支:
image.png

那這裏的 encode 方法在咱們遵照 Codable 協議以後,系統自動幫助咱們實現了,咱們經過 SIL 來看一下:
image.png
image.png
咱們再來看一下 LGPerson 默認的實現,因此天然而然咱們也就沒辦法訪問到,當前的 LGPerson 的編碼就會只對 age , name 進行編碼了。

class LGTeacher: LGPerson {
    var subjectName: String?
    
// init(name: String, age: Int, subjectName: String) {
// self.subjectName = subjectName
// super.init(name: name, age: age)
// }
//
// required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
// }
    
// enum CodingKeys: String, CodingKey{
// case subjectName
// }
    
    override func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(subjectName, forKey: .subjectName)
       let superdecoder = container.superEncoder()
       try super.encode(to: superdecoder)
    }
}
複製代碼

可是這樣寫的話,就會存在另外一個問題,由於當前的 CodingKeys 訪問不到,因此這裏咱們就須要這樣操做

class LGTeacher: LGPerson {
    var subjectName: String?
    
// init(name: String, age: Int, subjectName: String) {
// self.subjectName = subjectName
// super.init(name: name, age: age)
// }
//
// required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
// }
    
    enum CodingKeys: String, CodingKey{
        case subjectName
    }
    
    override func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(subjectName, forKey: .subjectName)
       let superdecoder = container.superEncoder()
       try super.encode(to: superdecoder)
    }
}
複製代碼

這樣咱們就能正確編碼了
image.png
若是咱們再把代碼修改一下:

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

class LGTeacher: LGPerson {
    var subjectName: String?
    
    init(name: String, age: Int, subjectName: String) {
        self.subjectName = subjectName
        super.init(name: name, age: age)
    }
    
    required init(from decoder: Decoder) throws {
        fatalError("init(from:) has not been implemented")
    }
    
    enum CodingKeys: String, CodingKey{
        case subjectName
    }
    
    override func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(subjectName, forKey: .subjectName)
       let superdecoder = container.superEncoder()
       try super.encode(to: superdecoder)
    }
}

class LGPartTimeTeacher: LGPerson{
    var partTime: Double?
}

let t: LGPerson = LGTeacher.init(name: "Kody", age: 10, subjectName: "Swift")

let encoder = JSONEncoder()
let encoderData = try encoder.encode(t)

print(String(data: encoderData, encoding: .utf8))

let t1: LGPerson = try JSONDecoder().decode(LGTeacher.self, from: encoderData)
複製代碼

能夠看到這裏,就直接報錯了:
image.png
看這裏好像是由於咱們沒有實現這個 decoder 的方法,那咱們來實現一下:

required init(from decoder: Decoder) throws {
// fatalError("init(from:) has not been implemented")
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.subjectName = try container.decode(String.self, forKey: .subjectName)
        
        try super.init(from: decoder)
    }
複製代碼
let t1: LGPerson = try JSONDecoder().decode(LGTeacher.self, from: encoderData)
print(t1.age)
print(t1.name)
複製代碼

image.png
並且若是當前屬性是非可選項,那麼這裏咱們就直接會獲得一個崩潰的應用程序。
image.png

若是這裏咱們換成結構體,那麼結果是否是同樣哪?咱們先來測試一下:

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

struct LGTeacher: LGPerson {
    var age: String
    var name: String
}

struct LGParTimeTeacher: LGPerson {
    var age: String
    var name: String
}

struct Company: Codable{
    var person: [LGPerson]
    var companyName: String
    
    enum CodingKeys: String, CodingKey {
        case person
        case companyName
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(person, forKey: .person)
        try container.encode(companyName, forKey: .companyName)
    }
    
}
複製代碼

當前編譯器就直接報錯了,由於當前 LGPerson 是個協議,他不能遵照他自身
image.png
那麼怎麼辦那?這個時候咱們可能想到的是直接在 LGTeacher , LGPartTimeTeacher 中實現 decode 和 encode 的方法,那咱們能夠找一箇中間層來解決這個問題:

protocol LGPerson{
    var age: String { get set }
    var name: String { get set }
}

struct LGTeacher: LGPerson {
    var age: String
    var name: String
}

struct LGParTimeTeacher: LGPerson {
    var age: String
    var name: String
}

struct LGPersonBox : LGPerson, Codable {
    
    var age: String
    var name: String

    init(_ person: LGPerson) {
        self.age = person.age
        self.name = person.name
    }
}

struct Company: Codable{
    var person: [LGPersonBox]
    var companyName: String
}
複製代碼
protocol LGPerson{
    var age: Int { get set }
    var name: String { get set }
}

struct LGTeacher: LGPerson {
    var age: Int
    var name: String
}

struct LGParTimeTeacher: LGPerson {
    var age: Int
    var name: String
}

struct LGPersonBox : LGPerson, Codable {
    
    var age: Int
    var name: String

    init(_ person: LGPerson) {
        self.age = person.age
        self.name = person.name
    }
}

struct Company: Codable{
    var person: [LGPersonBox]
    var companyName: String
}

let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]

let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted

let jsonData = try jsonEncoder.encode(company)

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

image.png
image.png

能夠看到這裏輸出的格式都是 LGPersonBox ,若是咱們想正確的還原咱們當前的類型信息,應該作什麼哪?很簡單的一種作法就是須要在編碼過程當中將咱們的類型信息編碼進去,什麼意思那?咱們經過代碼來看一下:

enum LGPersonType:String, Codable {
    case teacher
    case partTeacher

    var metdadata: LGPerson.Type {
        switch self {
        case .teacher:
            return LGTeacher.self
        case .partTeacher:
            return LGParTimeTeacher.self
        }
    }
}

protocol LGPerson: Codable{
    static var type: LGPersonType{ get }
    var age: Int { get set }
    var name: String { get set }
}

struct LGTeacher: LGPerson {
    static var type: LGPersonType = LGPersonType.teacher
    var age: Int
    var name: String
}

struct LGParTimeTeacher: LGPerson {
    static var type: LGPersonType = LGPersonType.partTeacher
    var age: Int
    var name: String
}

struct LGPersonBox : Codable {

    var p: LGPerson

    init(_ p: LGPerson) {
        self.p = p
    }

    private enum CodingKeys : CodingKey {
        case type
        case p
    }

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

        let type = try container.decode(LGPersonType.self, forKey: .type)
        self.p = try type.metdadata.init(from: container.superDecoder(forKey: .p))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(type(of: p).type, forKey: .type)
        try p.encode(to: container.superEncoder(forKey: .p))
    }
}

struct Company: Codable{
    var person: [LGPersonBox]
    var companyName: String
}

let person: [LGPerson] = [LGTeacher(age: 20, name: "Kody"),LGParTimeTeacher(age: 30, name: "Hank")]

let company = Company(person: person.map(LGPersonBox.init), companyName: "Logic")

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted

let jsonData = try jsonEncoder.encode(company)

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

固然若是咱們想輸出下面這個
image.png
image.png

新的實現方式

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 LGPersonType: String, Meta {
    typealias Element = LGPerson
    
    case teacher = "LGTeacher"
    case partTimeTeacher = "LGPartTimeTeacher"
    
    static func metatype(for typeString: String) -> LGPersonType {
        guard let metatype = self.init(rawValue: typeString) else {
            fatalError()
        }
        return metatype
    }
    
    var type: Decodable.Type {
        switch self {
        case .teacher:
            return LGTeacher.self
        case .partTimeTeacher:
            return LGPartTimeTeacher.self
        }
    }
}

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

class LGTeacher: LGPerson {
    var subjectName: String
    
    init(name: String, age: Int, subjectName: String) {
        self.subjectName = subjectName
        super.init(name: name, age: age)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        subjectName = try container.decode(String.self, forKey: .subjectName)
        
        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(subjectName, forKey: .subjectName)
        
        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case subjectName
    }
}

class LGPartTimeTeacher: LGPerson {
    var partTime: Double
    
    init(name: String, age: Int, partTime: Double) {
        self.partTime = partTime
        super.init(name: name, age: age)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        partTime = try container.decode(Double.self, forKey: .partTime)
    
        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(partTime, forKey: .partTime)
        
        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)
    }
    
    enum CodingKeys: String, CodingKey {
        case partTime
    }
}


let p: LGPerson = LGTeacher(name: "Kody", age: 20, subjectName: "Swift")
let jsonData = try JSONEncoder().encode(MetaObject<LGPersonType>(p))
if let str = String(data: jsonData, encoding: .utf8) {
    print(str)
}

let decode: MetaObject<LGPersonType> = try JSONDecoder().decode(MetaObject<LGPersonType>.self, from: jsonData)
複製代碼
相關文章
相關標籤/搜索