美文网首页swift mvvm
Swift底层进阶--015:Codable源码解析

Swift底层进阶--015:Codable源码解析

作者: 帅驼驼 | 来源:发表于2021-01-29 14:03 被阅读0次

    Codable协议在Swift4.0开始被引入,目的是取代NSCoding协议。Codable协议对Swift基本内嵌类型完美支持,能够把JSON弱类型数据转为代码中使用的强类型数据。

    Codable协议是EncodableDecodable协议的组合,如果实现了Codable,就表明实现了EncodableDecodable。如果想要实现自定义类型或数据模型的编码和解码,必须遵循Codable协议。

    Swift基本内嵌类型都默认遵循Codable协议,比如StringIntDoubleDateData。另外ArrayDictionaryOptional也都遵循Codable协议,可以直接使用编码和解码。

    常⻅⽤法

    定义LGTeacher结构体,遵循Codable协议

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

    JSONEncoder编码,将自定义类型转为JSON

    let t = LGTeacher.init(name: "Zang", age: 18)
    
    let jsonEncoder = JSONEncoder()
    let jsonData = try? jsonEncoder.encode(t)
    
    if let data = jsonData {
        let jsonString = String(decoding: data, as: UTF8.self)
        print(jsonString)
    }
    
    //输出以下内容:
    //{"name":"Zang","age":18}
    

    JSONDecoder解码,将JSON转为自定义类型

    let jsonString = """
    {
        "age": 18,
        "name": "Zang",
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    if let data = jsonData{
        
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(LGTeacher.self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //LGTeacher(name: "Zang", age: 18)
    
    嵌套的模型

    对于嵌套模型,必须保证所有模型都遵循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
        }
    }
    

    JSONEncoder编码

    let t = LGTeacher.init(name: "Zang", className: "Swift", courceCycle: 10, personInfo: LGTeacher.PersonInfo.init(age: 18, height: 1.85))
    
    let jsonEncoder = JSONEncoder()
    let jsonData = try? jsonEncoder.encode(t)
    
    if let data = jsonData {
        let jsonString = String(decoding: data, as: UTF8.self)
        print(jsonString)
    }
    
    //输出以下内容:
    //{"personInfo":{"age":18,"height":1.85},"courceCycle":10,"className":"Swift","name":"Zang"}
    

    JSONDecoder解码

    let jsonString = """
    {
        "name": "Zang",
        "className": "Swift",
        "courceCycle": 10,
        "personInfo": {
            "age": 18,
            "height": 1.85
        }
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
    
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(LGTeacher.self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //LGTeacher(name: "Zang", className: "Swift", courceCycle: 10, personInfo: xdgTestHelper.LGTeacher.PersonInfo(age: 18, height: 1.85))
    

    如果出现未遵循Codable协议的类型,例如PersonInfo,编译报错

    编译报错
    包含数组

    LGTeacher结构体内,包含personList数组类型

    struct LGTeacher: Codable{
        var name: String
        var className: String
        var courceCycle: Int
        var personList: [PersonInfo]
    }
    
    extension LGTeacher {
        struct PersonInfo: Codable {
            var age: Int
            var height: Double
        }
    }
    

    JSONDecoder解码

    let jsonString = """
    {
        "name": "Zang",
        "className": "Swift",
        "courceCycle": 10,
        "personList": [
            {
                "age": 18,
                "height": 1.85
            },{
                "age": 20,
                "height": 1.75
            }
        ]
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(LGTeacher.self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //LGTeacher(name: "Zang", className: "Swift", courceCycle: 10, personList: [xdgTestHelper.LGTeacher.PersonInfo(age: 18, height: 1.85), xdgTestHelper.LGTeacher.PersonInfo(age: 20, height: 1.75)])
    
    数组集合

    当解析类型为数组集合,在decode方法传入[LGTeacher].self即可

    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)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode([LGTeacher].self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //[xdgTestHelper.LGTeacher(name: "Kody", className: "Swift", courceCycle: 12), xdgTestHelper.LGTeacher(name: "Cat", className: "强化班", courceCycle: 15), xdgTestHelper.LGTeacher(name: "Hank", className: "逆向班", courceCycle: 22), xdgTestHelper.LGTeacher(name: "Cooci", className: "⼤师班", courceCycle: 22)]
    
    JSON数据中有Optional values

    日常开发中,和服务端对接数据难免出现null,如果处理不当很可能程序crash

    crash

    兼容方案:将可能为null的属性使用声明为Optional可选值

    Optional
    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": null,
            "courceCycle": 22
        },{
            "name": "Cooci",
            "className": "⼤师班",
            "courceCycle": 22
        }
    ]
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode([LGTeacher].self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //[xdgTestHelper.LGTeacher(name: "Kody", className: Optional("Swift"), courceCycle: 12), xdgTestHelper.LGTeacher(name: "Cat", className: Optional("强化班"), courceCycle: 15), xdgTestHelper.LGTeacher(name: "Hank", className: nil, courceCycle: 22), xdgTestHelper.LGTeacher(name: "Cooci", className: Optional("⼤师班"), courceCycle: 22)]
    
    元组类型

    ⽐如⼀个坐标,location : [20, 10],当使⽤Codable进⾏解析的过程中,需要实现init(from decoder: Decoder)方法

    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)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(RawSeverResponse.self, from: data)
        print(t ?? "解析失败")
    }
    
    //输出以下内容:
    //RawSeverResponse(location: xdgTestHelper.Location(x: 20.0, y: 10.0))
    

    使用unkeyedContainer,表示不解析当前的Key,也就是xy。然后单方面赋值给xy属性

    继承

    父类和子类无法同时遵循Codable协议,否则编译报错

    编译报错

    尝试让父类遵循Codable协议,是否可以解码正常?

    class LGTeacher: Codable {
        var name: String?
    }
    
    class LGChild: LGTeacher {
        var partTime: Int?
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "partTime": 20
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let c = try? jsonDecoder.decode(LGChild.self, from: data)
        print("name:\(c?.name),partTime:\(c?.partTime)")
    }
    
    //输出以下内容:
    //name:Optional("Zang"),partTime:nil
    

    上述代码,父类遵循Codable协议,仅父类的属性可以被解析,子类的属性被解析为nil

    如果让子类遵循Codable协议,是不是就正常了?

    class LGTeacher {
        var name: String?
    }
    
    class LGChild: LGTeacher, Codable {
        var partTime: Int?
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "partTime": 20
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let c = try? jsonDecoder.decode(LGChild.self, from: data)
        print("name:\(c?.name),partTime:\(c?.partTime)")
    }
    
    //输出以下内容:
    //name:nil,partTime:Optional(20)
    

    上述代码,子类遵循Codable协议,仅子类的属性可以被解析,父类的属性被解析为nil

    针对继承造成的编解码问题,在后面的Codable坑点分析里详细介绍,并提供解决方案

    协议

    当结构体遵循自定义协议,同时也遵循Codable协议,就可以成功编解码

    protocol LGProtocol {
        var name: String{ get set }
    }
    
    struct LGTeacher: LGProtocol, Codable {
        var name: String
        var partTime: Int?
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "partTime": 20
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()   
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(name: "Zang", partTime: Optional(20))
    

    可以让自定义协议遵循Codable协议,也能成功编解码

    protocol LGProtocol: Codable {
        var name: String{ get set }
    }
    
    struct LGTeacher: LGProtocol {
        var name: String
        var partTime: Int?
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "partTime": 20
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(name: "Zang", partTime: Optional(20))
    
    数据和对象存在结构差异

    遇到服务端返回数据和客户端对象存在结构差异的情况,可以这样处理:

    struct LGTeacher: 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)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(elements: ["Hank", "Cat", "Cooci", "Kody"])
    

    上述代码,实现init(from decoder: Decoder)方法自定义解析,让CodingKeys遵循CaseIterable协议,使枚举类型具有可遍历的特性。仅需要解码功能,可以只遵循Decodable协议

    源码解析
    Codable

    Codable定义:包含EncodableDecodable

    public typealias Codable = Decodable & Encodable
    
    Decodable

    Decodable:解码,用于弱类型数据向自定义类型的转换

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

    init方法内的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
    }
    
    JSONDecoder

    Decoder提供了一个解码器JSONDecoder,定义如下:

    open class JSONDecoder {
        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)
        }
    
        public enum DataDecodingStrategy {
            case deferredToData
            case base64
            case custom((_ decoder: Decoder) throws -> Data)
        }
    
        public enum NonConformingFloatDecodingStrategy {
            case `throw`
            case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
        }
        
        public enum KeyDecodingStrategy {
            case useDefaultKeys
            case convertFromSnakeCase
            case custom((_ codingPath: [CodingKey]) -> CodingKey)
            
            fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String {
                guard !stringKey.isEmpty else { return stringKey }
    
                guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else {
                    return stringKey
                }
    
                var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
                while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
                    stringKey.formIndex(before: &lastNonUnderscore)
                }
                
                let keyRange = firstNonUnderscore...lastNonUnderscore
                let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
                let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
                
                let components = stringKey[keyRange].split(separator: "_")
                let joinedString : String
                if components.count == 1 {
                    joinedString = String(stringKey[keyRange])
                } else {
                    joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
                }
    
                let result : String
                if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
                    result = joinedString
                } else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
                    result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
                } else if (!leadingUnderscoreRange.isEmpty) {
                    result = String(stringKey[leadingUnderscoreRange]) + joinedString
                } else {
                    result = joinedString + String(stringKey[trailingUnderscoreRange])
                }
                return result
            }
        }
        
        open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
        open var dataDecodingStrategy: DataDecodingStrategy = .base64
        open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw
        open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
        open var userInfo: [CodingUserInfoKey : Any] = [:]
    
        fileprivate struct _Options {
            let dateDecodingStrategy: DateDecodingStrategy
            let dataDecodingStrategy: DataDecodingStrategy
            let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
            let keyDecodingStrategy: KeyDecodingStrategy
            let userInfo: [CodingUserInfoKey : Any]
        }
    
        fileprivate var options: _Options {
            return _Options(dateDecodingStrategy: dateDecodingStrategy,
                            dataDecodingStrategy: dataDecodingStrategy,
                            nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
                            keyDecodingStrategy: keyDecodingStrategy,
                            userInfo: userInfo)
        }
    
        public init() {}
    
        open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
            let topLevel: Any
            do {
                topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
            } catch {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
            }
    
            let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
    
            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
        }
    }
    
    DateDecodingStrategy

    JSONDecoder类定义了DateDecodingStrategy枚举类型,使用何种策略解析日期格式,案例如下:

    struct LGTeacher: Codable {
        var date: Date
    }
    

    deferredToDate:默认策略

    let jsonString = """
    {
        "date": 1609183207
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.dateDecodingStrategy = .deferredToDate
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 2051-12-29 19:20:07 +0000)
    

    secondsSince1970:距离1970.01.01的秒数

    let jsonString = """
    {
        "date": 1609183207
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.dateDecodingStrategy = .secondsSince1970
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 2020-12-28 19:20:07 +0000)
    

    millisecondsSince1970:距离1970.01.01的毫秒数

    let jsonString = """
    {
        "date": 1609183207000
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.dateDecodingStrategy = .millisecondsSince1970
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 2020-12-28 19:20:07 +0000)
    

    iso8601:解码为ISO-8601格式(RFC 3339格式)

    let jsonString = """
    {
        "date": "1969-09-26T12:00:00Z"
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.dateDecodingStrategy = .iso8601
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 1969-09-26 12:00:00 +0000)
    

    formatted:后台自定义的格式,使用DateFormatter解析

    let jsonString = """
    {
        "date": "2020/12/28 19:20:00"
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
        
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        jsonDecoder.dateDecodingStrategy = .formatted(formatter)
        
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 2020-12-28 11:20:00 +0000)
    

    custom:自定义格式,通过闭包表达式返回Date类型

    let jsonString = """
    {
        "date": "2020/12/28 19:20:00"
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData {
        let jsonDecoder = JSONDecoder()
    
        jsonDecoder.dateDecodingStrategy = .custom(){decoder -> Date in
    
            let container = try decoder.singleValueContainer()
            let strDate = try container.decode(String.self)
            
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            
            guard let date = formatter.date(from: strDate) else {
                return Date()
            }
            
            return date
        }
        
        let t = try jsonDecoder.decode(LGTeacher.self, from: data)
        print(t)
    }
    
    //输出以下内容:
    //LGTeacher(date: 2020-12-28 11:20:00 +0000)
    
    DataDecodingStrategy

    DataDecodingStrategy:二进制解码策略

    • deferredToData:默认解码策略
    • base64:使用base64解码
    • custom:自定义方式解码
    NonConformingFloatDecodingStrategy

    NonConformingFloatDecodingStrategy:不合法浮点数的编码策略

    • throw
    • convertFromString
    KeyDecodingStrategy

    KeyDecodingStrategyKey的编码策略

    • useDefaultKeys
    • convertFromSnakeCase
    • custom
    decode

    decode方法用于将JSON转为指定类型,接收T.Type类型和Data数据

    open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
        let topLevel: Any
        do {
            topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
        } catch {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
        }
    
        let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
    
        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
    }
    
    • 入参的泛型T必须遵循Decodable协议
    • 使用JSONSerializationdata数据序列化为字典的KeyValue
    • 调用内部类_JSONDecoder传入字典和编码策略返回decoder对象
    • 通过decoder对象的unbox方法解码并返回value
    解码流程
    _JSONDecoder

    _JSONDecoder是用来解码操作的内部类,它遵循了Decoder协议

    fileprivate class _JSONDecoder : Decoder {
    
        fileprivate var storage: _JSONDecodingStorage
    
        fileprivate let options: JSONDecoder._Options
    
        fileprivate(set) public var codingPath: [CodingKey]
    
        public var userInfo: [CodingUserInfoKey : Any] {
            return self.options.userInfo
        }
    
        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
        }
    
        public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
            guard !(self.storage.topContainer is NSNull) else {
                throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self,
                                                  DecodingError.Context(codingPath: self.codingPath,
                                                                        debugDescription: "Cannot get keyed decoding container -- found null value instead."))
            }
    
            guard let topContainer = self.storage.topContainer as? [String : Any] else {
                throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer)
            }
    
            let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
            return KeyedDecodingContainer(container)
        }
    
        public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
            guard !(self.storage.topContainer is NSNull) else {
                throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
                                                  DecodingError.Context(codingPath: self.codingPath,
                                                                        debugDescription: "Cannot get unkeyed decoding container -- found null value instead."))
            }
    
            guard let topContainer = self.storage.topContainer as? [Any] else {
                throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer)
            }
    
            return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
        }
    
        public func singleValueContainer() throws -> SingleValueDecodingContainer {
            return self
        }
    }
    

    init方法,有三个参数传入

    • container:序列化后的KeyValue
    • codingPathCodingKey类型的空数组
    • 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
    }
    
    • 创建内部类_JSONDecodingStorage
    • 使用push方法存储要解码的数据

    _JSONDecodingStorage是一个结构体,内部有Any类型数组可存放任意类型,提供pushpopContainer等方法,相当于一个容器

    fileprivate struct _JSONDecodingStorage {
        private(set) fileprivate var containers: [Any] = []
    
        fileprivate init() {}
    
        fileprivate var count: Int {
            return self.containers.count
        }
    
        fileprivate var topContainer: Any {
            precondition(!self.containers.isEmpty, "Empty container stack.")
            return self.containers.last!
        }
    
        fileprivate mutating func push(container: Any) {
            self.containers.append(container)
        }
    
        fileprivate mutating func popContainer() {
            precondition(!self.containers.isEmpty, "Empty container stack.")
            self.containers.removeLast()
        }
    }
    

    unbox方法用于解码操作,匹配对应的类型然后执行条件分支

    fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
        return try unbox_(value, as: type) as? T
    }
    
    fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
        #if DEPLOYMENT_RUNTIME_SWIFT
    
        if type == Date.self {
            guard let date = try self.unbox(value, as: Date.self) else { return nil }
            return date
        } else if type == Data.self {
            guard let data = try self.unbox(value, as: Data.self) else { return nil }
            return data
        } else if type == URL.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 {
            guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
            return decimal
        } 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)
        }
        #else
        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)
        }
        #endif
    }
    

    例如:针对Date类型的解码,内部会根据不同编码策略,执行不同的代码分支

    fileprivate func unbox(_ value: Any, as type: Date.Type) throws -> Date? {
        guard !(value is NSNull) else { return nil }
    
        switch self.options.dateDecodingStrategy {
        case .deferredToDate:
            self.storage.push(container: value)
            defer { self.storage.popContainer() }
            return try Date(from: self)
    
        case .secondsSince1970:
            let double = try self.unbox(value, as: Double.self)!
            return Date(timeIntervalSince1970: double)
    
        case .millisecondsSince1970:
            let double = try self.unbox(value, as: Double.self)!
            return Date(timeIntervalSince1970: double / 1000.0)
    
        case .iso8601:
            if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
                let string = try self.unbox(value, as: String.self)!
                guard let date = _iso8601Formatter.date(from: string) else {
                    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
                }
    
                return date
            } else {
                fatalError("ISO8601DateFormatter is unavailable on this platform.")
            }
    
        case .formatted(let formatter):
            let string = try self.unbox(value, as: String.self)!
            guard let date = formatter.date(from: string) else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Date string does not match format expected by formatter."))
            }
    
            return date
    
        case .custom(let closure):
            self.storage.push(container: value)
            defer { self.storage.popContainer() }
            return try closure(self)
        }
    }
    

    unbox方法内有一个代码分支,针对_JSONStringDictionaryDecodableMarker类型进行解码

    else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
        return try self.unbox(value, as: stringKeyedDictType)
    }
    

    查看_JSONStringDictionaryDecodableMarker的定义

    fileprivate protocol _JSONStringDictionaryEncodableMarker { }
    
    extension Dictionary : _JSONStringDictionaryEncodableMarker where Key == String, Value: Encodable { }
    
    fileprivate protocol _JSONStringDictionaryDecodableMarker {
        static var elementType: Decodable.Type { get }
    }
    
    extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, Value: Decodable {
        static var elementType: Decodable.Type { return Value.self }
    }
    

    _JSONStringDictionaryEncodableMarker是一个协议,并且字典扩展遵循了这个协议,有两个限制条件

    • Key必须为String类型
    • Value必须遵循Decodable协议

    查看针对_JSONStringDictionaryDecodableMarker类型的解码方法

    fileprivate func unbox<T>(_ value: Any, as type: _JSONStringDictionaryDecodableMarker.Type) throws -> T? {
        guard !(value is NSNull) else { return nil }
    
        var result = [String : Any]()
        guard let dict = value as? NSDictionary else {
            throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
        }
        let elementType = type.elementType
        for (key, value) in dict {
            let key = key as! String
            self.codingPath.append(_JSONKey(stringValue: key, intValue: nil))
            defer { self.codingPath.removeLast() }
    
            result[key] = try unbox_(value, as: elementType)
        }
    
        return result as? T
    }
    

    对于_JSONStringDictionaryDecodableMarker类型的解码过程,其实就是一个递归操作

    分析SIL代码
    struct LGTeacher: Codable{
        var name: String
        var age: Int
    }
    
    let jsonString = """
    {
        "age": 18,
        "name": "Zang",
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    let jsonDecoder = JSONDecoder()
    let t = try? jsonDecoder.decode(LGTeacher.self, from: jsonData!)
    

    将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

    struct LGTeacher : Decodable & Encodable {
      @_hasStorage var name: String { get set }
      @_hasStorage var age: Int { get set }
      init(name: String, age: Int)
      enum CodingKeys : CodingKey {
        case name
        case age
        @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: LGTeacher.CodingKeys, _ b: LGTeacher.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)
      }
      init(from decoder: Decoder) throws
      func encode(to encoder: Encoder) throws
    }
    
    • 编译器自动实现CodingKeys枚举类型,并遵循CodingKey协议。解码过程中会通过CodingKeys找到对应case
    • 编译器自动实现decode解码方法:init(from decoder: Decoder)
    • 编译器自动实现encode编码方法:encode(to encoder: Encoder)

    源码中type.init(from:)方法,传入的self,本质是_JSONDecoder

    return try type.init(from: self)
    

    查看源码中type.init(from:)方法的SIL代码实现:

    // LGTeacher.init(from:)
    sil hidden @main.LGTeacher.init(from: Swift.Decoder) throws -> main.LGTeacher : $@convention(method) (@in Decoder, @thin LGTeacher.Type) -> (@owned LGTeacher, @error Error) {
    // %0 "decoder"                                   // users: %69, %49, %9, %6
    // %1 "$metatype"
    bb0(%0 : $*Decoder, %1 : $@thin LGTeacher.Type):
      %2 = alloc_stack $Builtin.Int2                  // users: %70, %27, %5, %78, %52
      %3 = alloc_stack [dynamic_lifetime] $LGTeacher, var, name "self" // users: %40, %24, %50, %73, %77, %51
      %4 = integer_literal $Builtin.Int2, 0           // user: %5
      store %4 to %2 : $*Builtin.Int2                 // id: %5
      debug_value_addr %0 : $*Decoder, let, name "decoder", argno 1 // id: %6
      debug_value undef : $Error, var, name "$error", argno 2 // id: %7
      %8 = alloc_stack $KeyedDecodingContainer<LGTeacher.CodingKeys>, let, name "container" // users: %45, %44, %37, %66, %65, %21, %60, %59, %13, %55
      %9 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("2D27860A-6118-11EB-ACCF-34363BD04690") Decoder // users: %13, %13, %12
      %10 = metatype $@thin LGTeacher.CodingKeys.Type
      %11 = metatype $@thick LGTeacher.CodingKeys.Type // user: %13
      %12 = witness_method $@opened("2D27860A-6118-11EB-ACCF-34363BD04690") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("2D27860A-6118-11EB-ACCF-34363BD04690") 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: %9; user: %13
      try_apply %12<@opened("2D27860A-6118-11EB-ACCF-34363BD04690") Decoder, LGTeacher.CodingKeys>(%8, %11, %9) : $@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 bb4 // type-defs: %9; id: %13
    
    • 创建$KeyedDecodingContainer的临时常量container
    • PWT协议目击表中找到container方法并调用

    源码中可以看到_JSONDecoder确实遵循了Decoder协议

    _JSONDecoder

    Decoder协议中存在container方法的声明

    Decoder

    查看源码中_JSONDecodercontainer方法,返回KeyedDecodingContainer<Key>

    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<K>是一个结构体,遵循KeyedDecodingContainerProtocol协议。有一个条件限制,K必须遵循CodingKey协议。结构体内定义各种类型的解码方法,会根据不同类型匹配到对应的decode方法

    public struct KeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K : CodingKey {
        public typealias Key = K
        public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedDecodingContainerProtocol
        public var codingPath: [CodingKey] { get }
        public var allKeys: [KeyedDecodingContainer<K>.Key] { get }
        public func contains(_ key: KeyedDecodingContainer<K>.Key) -> Bool
        public func decodeNil(forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
        public func decode(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
        public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String
        public func decode(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double
        public func decode(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float
        public func decode(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int
        public func decode(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8
        public func decode(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16
        public func decode(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32
        public func decode(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64
        public func decode(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt
        public func decode(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8
        public func decode(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16
        public func decode(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32
        public func decode(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64
        public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T where T : Decodable
        public func decodeIfPresent(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool?
        public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?
        public func decodeIfPresent(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double?
        public func decodeIfPresent(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float?
        public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?
        public func decodeIfPresent(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8?
        public func decodeIfPresent(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16?
        public func decodeIfPresent(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32?
        public func decodeIfPresent(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64?
        public func decodeIfPresent(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt?
        public func decodeIfPresent(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8?
        public func decodeIfPresent(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16?
        public func decodeIfPresent(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32?
        public func decodeIfPresent(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64?
        public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T : Decodable
        public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey
        public func nestedUnkeyedContainer(forKey key: KeyedDecodingContainer<K>.Key) throws -> UnkeyedDecodingContainer
        public func superDecoder() throws -> Decoder
        public func superDecoder(forKey key: KeyedDecodingContainer<K>.Key) throws -> Decoder
        public func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool?
        public func decodeIfPresent(_ type: String.Type, forKey key: K) throws -> String?
        public func decodeIfPresent(_ type: Double.Type, forKey key: K) throws -> Double?
        public func decodeIfPresent(_ type: Float.Type, forKey key: K) throws -> Float?
        public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int?
        public func decodeIfPresent(_ type: Int8.Type, forKey key: K) throws -> Int8?
        public func decodeIfPresent(_ type: Int16.Type, forKey key: K) throws -> Int16?
        public func decodeIfPresent(_ type: Int32.Type, forKey key: K) throws -> Int32?
        public func decodeIfPresent(_ type: Int64.Type, forKey key: K) throws -> Int64?
        public func decodeIfPresent(_ type: UInt.Type, forKey key: K) throws -> UInt?
        public func decodeIfPresent(_ type: UInt8.Type, forKey key: K) throws -> UInt8?
        public func decodeIfPresent(_ type: UInt16.Type, forKey key: K) throws -> UInt16?
        public func decodeIfPresent(_ type: UInt32.Type, forKey key: K) throws -> UInt32?
        public func decodeIfPresent(_ type: UInt64.Type, forKey key: K) throws -> UInt64?
        public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable
    }
    

    上述代码,结构体中定义了很多类型的decode方法,这些方法由苹果内部工具生成,利用Codable.swift.gyb模板文件生成Codable.swift源文件

    Codable.swift.gyb模板文件:

    定义集合存放所有可编解码的内嵌类型。以%开始和结束,视为代码的开始和结束,通过python控制,相当于模板文件

    没有以%开始和结束,视为文本直接输出

    同样是模板文件,遍历集合里的内嵌类型,${type}输出对应类型的文本,例如BoolInt32

    当自己实现init(from decoder: Decoder)方法时,其中decodeCodingKeys都由系统自动生成

    let jsonString = """
    {
        "name": "Zang",
        "age": 18,
    }
    """
    
    struct LGTeacher: Codable{
        var name: String
        var age: Int
        
        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(Int.self, forKey: .age)
        }
    }
    

    也可以重新定义CodingKeys来解决数据和对象存在结构差异的情况

    let jsonString = """
    {
        "Nickname": "Zang",
        "age": 18,
    }
    """
    
    struct LGTeacher: Codable{
        var name: String
        var age: Int
        
        enum CodingKeys: String, CodingKey {
            case name = "Nickname"
            case age
        }
        
        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(Int.self, forKey: .age)
        }
    }
    
    Encodable

    Encodable:编码,用于自定义类型向弱类型数据的转换

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

    encode方法,接收泛型T,泛型必须遵循Encodable协议,返回Data数据

    open func encode<T : Encodable>(_ value: T) throws -> Data {
        let encoder = _JSONEncoder(options: self.options)
    
        guard let topLevel = try encoder.box_(value) else {
            throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
        }
    
        let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)
    
        do {
            return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
        } catch {
            throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
        }
    }
    
    • 创建内部类_JSONEncoder
    • 调用box_方法包装成字典类型
    • 使用JSONSerialization序列化为Data数据
    _JSONEncoder

    _JSONEncoder类遵循Encoder协议,主要提供container编码方法,返回KeyedEncodingContainer<Key>

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

    KeyedEncodingContainer<K>结构体,遵循KeyedEncodingContainerProtocol协议,要求K必须遵循CodingKey协议,内部定义了各种类型对应的encode方法

    public struct KeyedEncodingContainer<K> : KeyedEncodingContainerProtocol where K : CodingKey {
        public typealias Key = K
        public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedEncodingContainerProtocol
        public var codingPath: [CodingKey] { get }
        public mutating func encodeNil(forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Bool, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: String, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Double, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Float, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Int, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Int8, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Int16, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Int32, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: Int64, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: UInt, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: UInt8, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: UInt16, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: UInt32, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode(_ value: UInt64, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encode<T>(_ value: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
        public mutating func encodeConditional<T>(_ object: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : AnyObject, T : Encodable
        public mutating func encodeIfPresent(_ value: Bool?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: String?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Double?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Float?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Int?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Int8?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Int16?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Int32?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: Int64?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: UInt?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: UInt8?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: UInt16?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: UInt32?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent(_ value: UInt64?, forKey key: KeyedEncodingContainer<K>.Key) throws
        public mutating func encodeIfPresent<T>(_ value: T?, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
        public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: KeyedEncodingContainer<K>.Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey
        public mutating func nestedUnkeyedContainer(forKey key: KeyedEncodingContainer<K>.Key) -> UnkeyedEncodingContainer
        public mutating func superEncoder() -> Encoder
        public mutating func superEncoder(forKey key: KeyedEncodingContainer<K>.Key) -> Encoder
        public mutating func encodeConditional<T>(_ object: T, forKey key: K) throws where T : AnyObject, T : Encodable
        public mutating func encodeIfPresent(_ value: Bool?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: String?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Double?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Float?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Int?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Int8?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Int16?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Int32?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: Int64?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: UInt?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: UInt8?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: UInt16?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: UInt32?, forKey key: K) throws
        public mutating func encodeIfPresent(_ value: UInt64?, forKey key: K) throws
        public mutating func encodeIfPresent<T>(_ value: T?, forKey key: K) throws where T : Encodable
    }
    
    box_

    box_方法,根据value的不同类型,调用不同的代码分支,将value包装成对应的数据类型

    fileprivate func box_(_ value: Encodable) throws -> NSObject? {
        let type = Swift.type(of: value)
        #if DEPLOYMENT_RUNTIME_SWIFT
        if type == Date.self {
            // Respect Date encoding strategy
            return try self.box((value as! Date))
        } else if type == Data.self {
            // Respect Data encoding strategy
            return try self.box((value as! Data))
        } else if type == URL.self {
            // Encode URLs as single strings.
            return self.box((value as! URL).absoluteString)
        } else if type == Decimal.self {
            // JSONSerialization can consume NSDecimalNumber values.
            return NSDecimalNumber(decimal: value as! Decimal)
        } else if value is _JSONStringDictionaryEncodableMarker {
            return try box(value as! [String : Encodable])
        }
        
        #else
        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 {
            // JSONSerialization can consume NSDecimalNumber values.
            return NSDecimalNumber(decimal: value as! Decimal)
        } else if value is _JSONStringDictionaryEncodableMarker {
            return try box(value as! [String : Encodable])
        }
        #endif
        
        // 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()
    }
    

    上述代码,如果value不是上述定义的数据类型,例如LGTeacher,最终会调用value.encode(to: self)方法,传入的self就是_JSONEncoder

    box_方法内有一个代码分支,针对_JSONStringDictionaryEncodableMarker类型进行编码

    else if value is _JSONStringDictionaryEncodableMarker {
        return try box(value as! [String : Encodable])
    }
    

    查看_JSONStringDictionaryEncodableMarker的定义

    fileprivate protocol _JSONStringDictionaryEncodableMarker { }
    
    extension Dictionary : _JSONStringDictionaryEncodableMarker where Key == String, Value: Encodable { }
    
    fileprivate protocol _JSONStringDictionaryDecodableMarker {
        static var elementType: Decodable.Type { get }
    }
    
    extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, Value: Decodable {
        static var elementType: Decodable.Type { return Value.self }
    }
    

    _JSONStringDictionaryEncodableMarker是一个协议,并且字典扩展遵循了这个协议,有两个限制条件

    • Key必须为String类型
    • Value必须遵循Encodable协议

    查看针对_JSONStringDictionaryEncodableMarker类型的编码方法

    fileprivate func box(_ dict: [String : Encodable]) throws -> NSObject? {
        let depth = self.storage.count
        let result = self.storage.pushKeyedContainer()
        do {
            for (key, value) in dict {
                self.codingPath.append(_JSONKey(stringValue: key, intValue: nil))
                defer { self.codingPath.removeLast() }
                result[key] = try box(value)
            }
        } 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()
    }
    

    对于_JSONStringDictionaryEncodableMarker类型的编码过程,通过遍历将key打包为String类型,将value打包成相应的数据类型,最终返回最后一个元素

    整体encode编码流程,相当于跟decode解码流程是完全相反的逆过程

    编码流程
    Codable坑点分析

    通过继承的案例,进行Codable的坑点分析

    class LGPerson: Codable {
        var name: String?
        var age: Int?
    }
    
    class LGTeacher: LGPerson {
        var subjectName: String?
    }
    
    let t = LGTeacher()
    t.age = 10
    t.name = "Zang"
    t.subjectName = "Swift"
    
    let jsonEncoder = JSONEncoder()
    let jsonData = try? jsonEncoder.encode(t)
    
    if let data = jsonData{
        let jsonString = String(data: data, encoding: .utf8)
        print(jsonString ?? "解析失败")
    }
    
    //输出以下结果:
    //{"name":"Zang","age":10}
    

    上述代码,仅能编码出agename属性,但subjectName属性无法正常编码

    原因:

    • 父类LGPerson遵循了Codable协议,所以系统针对LGPerson自动生成了encode(to encoder: Encoder)方法
    • 子类LGTeacher虽然继承自LGPerson,但并没有重写encode(to encoder: Encoder)方法
    • 所以在编码过程中,找到的依然是父类的encode方法,最终仅父类属性可以被成功编码

    将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

    class LGPerson : 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: LGPerson.CodingKeys, _ b: LGPerson.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
    }
    

    针对LGPerson,系统自动生成了CodingKeysencode(to encoder: Encoder)方法

    @_inheritsConvenienceInitializers class LGTeacher : LGPerson {
      @_hasStorage @_hasInitialValue var subjectName: String? { get set }
      @objc deinit
      override init()
      required init(from decoder: Decoder) throws
    }
    

    但对于LGTeacher,仅存在init(from decoder: Decoder)方法

    可以通过重写子类的encode(to encoder: Encoder)方法解决

    class LGPerson: Codable {
        var name: String?
        var age: Int?
    }
    
    class LGTeacher: LGPerson {
        var subjectName: String?
        
        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)
            try super.encode(to: encoder)
        }
    }
    
    let t = LGTeacher()
    t.age = 10
    t.name = "Zang"
    t.subjectName = "Swift"
    
    let jsonEncoder = JSONEncoder()
    let jsonData = try? jsonEncoder.encode(t)
    
    if let data = jsonData{
        let jsonString = String(data: data, encoding: .utf8)
        print(jsonString ?? "解析失败")
    }
    
    //输出以下结果:
    //{"subjectName":"Swift","name":"Zang","age":10}
    

    如果在super.encode方法中,使用container.superEncoder(),在编码后的JSON数据里也会增加super节点,这里不推荐使用

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(subjectName, forKey: .subjectName)
        try super.encode(to: container.superEncoder())
    }
    
    //输出以下结果:
    //{"subjectName":"Swift","super":{"name":"Zang","age":10}}
    

    修改案例,如果将LGTeacher进行解码操作,能否正常解析所有属性?

    class LGPerson: Codable {
        var name: String?
        var age: Int?
    }
    
    class LGTeacher: LGPerson {
        var subjectName: String?
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "age": 18,
        "subjectName": "Swift",
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(LGTeacher.self, from: data)
        print("name:\(t?.name),age:\(t?.age),subjectName:\(t?.subjectName)")
    }
    
    //输出以下内容:
    //name:Optional("Zang"),age:Optional(18),subjectName:nil
    

    仅父类的属性被成功解析,子类的subjectName属性被解析为nil

    可以通过重写子类的init(from decoder: Decoder)方法解决

    class LGPerson: Codable {
        var name: String?
        var age: Int?
    }
    
    class LGTeacher: LGPerson {
        var subjectName: String?
    
        enum CodingKeys: String, CodingKey{
            case subjectName
        }
    
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.subjectName = try container.decode(String.self, forKey: .subjectName)
            try super.init(from: decoder)
        }
    }
    
    let jsonString = """
    {
        "name": "Zang",
        "age": 18,
        "subjectName": "Swift",
    }
    """
    
    let jsonData = jsonString.data(using: .utf8)
    
    if let data = jsonData{
        let jsonDecoder = JSONDecoder()
        let t = try? jsonDecoder.decode(LGTeacher.self, from: data)
        print("name:\(t?.name),age:\(t?.age),subjectName:\(t?.subjectName)")
    }
    
    //输出以下内容:
    //name:Optional("Zang"),age:Optional(18),subjectName:Optional("Swift")
    
    多态模式下的编解码问题

    当结构体存储自定义协议,即使协议遵循Codable协议,依然编译报错,提示:协议类型不符合Decodable协议,只允许使用structenumclass

    编译报错

    LGPerson是个协议,无法实现initencode方法

    这时可以考虑使用中间层来解决问题

    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
    }
    
    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)")
    }
    
    print("\n--------------------\n")
    
    let jsonDecoder = JSONDecoder()
    let c = try? jsonDecoder.decode(Company.self, from: jsonData)
    print("解码:\(c)")
    
    //输出以下结果:
    //编码:{
    //  "companyName" : "Logic",
    //  "person" : [
    //    {
    //      "age" : "20",
    //      "name" : "Kody"
    //    },
    //    {
    //      "age" : "30",
    //      "name" : "Hank"
    //    }
    //  ]
    //}
    //
    //--------------------
    //
    //解码:Optional(LGSwiftTest.Company(person: [LGSwiftTest.LGPersonBox(age: "20", name: "Kody"), LGSwiftTest.LGPersonBox(age: "30", name: "Hank")], companyName: "Logic"))
    

    上述代码,编码和解码都能执行成功,但解码后输出的类型都是LGPersonBox。如果需要保留原始的类型信息,应该怎样处理?

    方案1:使用unBox方法还原类型

    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 {
        var type: LGPersonType { get }
        var age: String { get set }
        var name: String { get set }
    }
    
    struct LGTeacher: LGPerson {
        var type: LGPersonType = LGPersonType.teacher
        var age: String
        var name: String
    
        static func unBox(_ person: LGPerson) -> LGPerson {
            return LGTeacher(age: person.age, name: person.name)
        }
    }
    
    struct LGParTimeTeacher: LGPerson {
        var type: LGPersonType = LGPersonType.partTeacher
        var age: String
        var name: String
    
        static func unBox(_ person: LGPerson) -> LGPerson {
            return LGParTimeTeacher(age: person.age, name: person.name)
        }
    }
    
    struct LGPersonBox: LGPerson, Codable {
        var type: LGPersonType
        var age: String
        var name: String
    
        init(_ person: LGPerson) {
            self.type = person.type
            self.age = person.age
            self.name = person.name
        }
        
        static func unBox(_ person: LGPerson) -> LGPerson {
    
            if person.type.metdadata == LGTeacher.self {
                return LGTeacher.unBox(person)
            }
            
            return LGParTimeTeacher.unBox(person)
        }
    }
    
    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)")
    }
    
    print("\n--------------------\n")
    
    let jsonDecoder = JSONDecoder()
    let c = try? jsonDecoder.decode(Company.self, from: jsonData)
    print("解码:\(c?.person.map{ LGPersonBox.unBox($0) })")
    
    //输出以下结果:
    //编码:{
    //  "companyName" : "Logic",
    //  "person" : [
    //    {
    //      "type" : "teacher",
    //      "age" : "20",
    //      "name" : "Kody"
    //    },
    //    {
    //      "type" : "partTeacher",
    //      "age" : "30",
    //      "name" : "Hank"
    //    }
    //  ]
    //}
    //
    //--------------------
    //
    //解码:Optional([LGSwiftTest.LGTeacher(type: LGSwiftTest.LGPersonType.teacher, age: "20", name: "Kody"), LGSwiftTest.LGParTimeTeacher(type: LGSwiftTest.LGPersonType.partTeacher, age: "30", name: "Hank")])
    

    方案2:可以在编码过程中将类型信息编码进去

    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)")
    }
    
    print("\n--------------------\n")
    
    let jsonDecoder = JSONDecoder()
    let c = try? jsonDecoder.decode(Company.self, from: jsonData)
    print("解码:\(c)")
    
    //输出以下结果:
    //编码:{
    //  "companyName" : "Logic",
    //  "person" : [
    //    {
    //      "type" : "teacher",
    //      "p" : {
    //        "age" : 20,
    //        "name" : "Kody"
    //      }
    //    },
    //    {
    //      "type" : "partTeacher",
    //      "p" : {
    //        "age" : 30,
    //        "name" : "Hank"
    //      }
    //    }
    //  ]
    //}
    //
    //--------------------
    //
    //解码:Optional(LGSwiftTest.Company(person: [LGSwiftTest.LGPersonBox(p: LGSwiftTest.LGTeacher(age: 20, name: "Kody")), LGSwiftTest.LGPersonBox(p: LGSwiftTest.LGParTimeTeacher(age: 30, name: "Hank"))], companyName: "Logic"))
    

    这里提供另一个实现方式

    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)
        }
        
        enum CodingKeys: String, CodingKey {
            case subjectName
        }
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            subjectName = try container.decode(String.self, forKey: .subjectName)
            try super.init(from: decoder)
        }
        
        override func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(subjectName, forKey: .subjectName)
            try super.encode(to: encoder)
        }
    }
    
    class LGPartTimeTeacher: LGPerson {
        var partTime: Double
        
        init(name: String, age: Int, partTime: Double) {
            self.partTime = partTime
            super.init(name: name, age: age)
        }
        
        enum CodingKeys: String, CodingKey {
            case partTime
        }
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            partTime = try container.decode(Double.self, forKey: .partTime)
            try super.init(from: decoder)
        }
        
        override func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(partTime, forKey: .partTime)
            try super.encode(to: encoder)
        }
    }
    
    struct Company<M: Meta>: Codable{
        private var person: [MetaObject<M>]
        
        init() {
            self.person = []
        }
        
        mutating func add(p: LGPerson) {
            person.append(MetaObject<M>(p as! M.Element))
        }
    }
    
    
    let p1: LGPerson = LGTeacher(name: "Kody", age: 20, subjectName: "Swift")
    let p2: LGPerson = LGPartTimeTeacher(name: "Cat", age: 30, partTime: 1.85)
    var company: Company = Company<LGPersonType>()
    company.add(p: p1)
    company.add(p: p2)
    
    let jsonEncoder = JSONEncoder()
    jsonEncoder.outputFormatting = .prettyPrinted
    
    let jsonData = try jsonEncoder.encode(company)
    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print("编码:\(jsonString)")
    }
    
    print("\n--------------------\n")
    
    let jsonDecoder = JSONDecoder()
    let c = try? jsonDecoder.decode(Company<LGPersonType>.self, from: jsonData)
    print("解码:\(c)")
    
    //输出以下结果:
    //编码:{
    //  "person" : [
    //    {
    //      "metatype" : "LGTeacher",
    //      "object" : {
    //        "subjectName" : "Swift",
    //        "name" : "Kody",
    //        "age" : 20
    //      }
    //    },
    //    {
    //      "metatype" : "LGPartTimeTeacher",
    //      "object" : {
    //        "partTime" : 1.8500000000000001,
    //        "name" : "Cat",
    //        "age" : 30
    //      }
    //    }
    //  ]
    //}
    //
    //--------------------
    //
    //解码:Optional(LGSwiftTest.Company<LGSwiftTest.LGPersonType>(person: [LGSwiftTest.MetaObject<LGSwiftTest.LGPersonType>(object: LGSwiftTest.LGTeacher), LGSwiftTest.MetaObject<LGSwiftTest.LGPersonType>(object: LGSwiftTest.LGPartTimeTeacher)]))
    
    和其他编解码类库的对比
    • SwiftyJSON:使用下标的方式取值,很容易数组越界
    • ObjectMapper:手动对每一个对象提供映射关系,代码量很大
    • HandyJSON:使用内存赋值的方式进行编解码操作,原理和Codable殊途同归
    • Codable:遇到继承和多态模式,需要手动实现编解码功能

    以上类库相比较而言,HandyJSONCodable更具优势

    相关文章

      网友评论

        本文标题:Swift底层进阶--015:Codable源码解析

        本文链接:https://www.haomeiwen.com/subject/oltvzktx.html