Swift-Coadable源码解析

801 阅读11分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

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

**

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

这里我们使用一个简单的案例来看一下:

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 创建出来的对象

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

这里我们来看一下实际的使用场景:

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.png

  • 这里是一个泛型函数,传入的参数 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

编码流程处理

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)