Swift-Codable源码解析

3,832 阅读13分钟

Codable简介

Codable是在Swift4.0开始被引入的新特性,目的是取代NSCoding协议。

  • Codable能够将程序内部的数据结构序列化成可交换数据,也能够将通用数据格式反序列化为内部使用的数据结构,大大提升对象和其表示之间互相转换的体验。

  • Codable协议对Swift基本内嵌类型完美支持,它对结构体,枚举和类都支持,能够把JSON这种弱类型数据转换成代码中使用的强类型数据,同时由于编译器的帮助,可以使开发者少写很多重复代码。

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

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

/// A type that can convert itself into and out of an external representation.
/// `Codable` is a type alias for the `Encodable` and `Decodable` protocols.
/// When you use `Codable` as a type or a generic constraint, it matches
/// any type that conforms to both protocols.

public typealias Codable = Decodable & Encodable

/// A type that can decode itself from an external representation.
public protocol Decodable {

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    init(from decoder: Decoder) throws
}

/// A type that can encode itself to an external representation.
public protocol Encodable {

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}

基本用法

看上面代码:

Decodable

Decodable协议定义了一个初始化函数:

init(from decoder: Decoder) throws

遵从Decodable协议的类型可以使用任何Decoder对象进行初始化,完成一个解码过程。 例如:

struct CPerson: Decodable{
    var name: String
    var age: Int
}

let jsonString = """
{
    "age": 18,
    "name": "吴彦祖",
}
"""

let jsonData = jsonString.data(using: .utf8)
if let data = jsonData{
    let jsonDecoder = JSONDecoder()
    let t = try? jsonDecoder.decode(CPerson.self, from: data)
    print(t ?? "解析失败")
}

JSONDecoder是一个解析器,他可以解析实现Decodable协议的任何类型。

Encodable

Encodable协议定义了一个方法:

func encode(to encoder: Encoder) throws

任何Encoder对象,只要创建的时候遵从了Encodable协议类型,就表示完成一个编码过程。 由于Swift标准库中的类型,比如String,Int,Double和 Foundation 框架中Data,Date,URL都是默认支持Codable协议的,所以只需声明支持协议即可。

let t = CPerson(name: "刘", 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)
}

JSONEncoder是一个编码器,他可以解析实现Encodable协议的任何类型。

嵌套的模型

对于嵌套模型,必须保证所有模型都遵循Codable协议,才能进行编码和解码。 如果出现未遵循Codable协议的类型,编译报错。

JSON数据中有Optional values

日常开发中,和服务端对接数据难免出现null,如果处理不当很可能程序crash 兼容方案:将可能为null的属性使用声明为Optional可选值

包含数组

保证数组的Item都遵循Codable协议,才能进行编码和解码。

数组集合

当解析类型为数组集合,在decode方法传入[<Type>].self即可 例如:

let t = try? jsonDecoder.decode([CPerson].self, from: data)

继承

父类和子类无法同时遵循Codable协议,否则编译报错 如果父类继承了Codable,则子类的成员无法解析和编码。原因很简单,就是由于子类没实现协议函数。

class CPerson: Codable {
    var name: String?
}

class SuperMan: CPerson {
    var color: String?
}

let jsonString = """
{
    "name": "Zang",
    "color": "Red"
}
"""

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

if let data = jsonData{
    let jsonDecoder = JSONDecoder()
    let c = try? jsonDecoder.decode(SuperMan.self, from: data)
    print("name:\(c?.name),partTime:\(c?.color)")
}

///result
name:Optional("Zang"),color:nil

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

同理:子类遵循Codable协议,仅子类的属性可以被解析,父类的属性被解析为nil 怎么解决,我们后面再说。

元组类型

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

struct Point: Codable {
    var x: Double
    var y: Int

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

struct RawSeverResponse: Codable{
    var location: Point
}

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 ?? "解析失败")
}

如上代码,使用unkeyedContainer,表示不解析当前的Key,也就是xy。然后单方面赋值给xy属性,解析元组,不能使用直接和swift元组对应,只能通过结构体

协议

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

protocol CProtocol {
    var name: String{ get set }
}

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

struct CPersion: CProtocol, Codable {
    var name: String
    var partTime: Int?
}

struct BPersion: BProtocol {
    var name: String
    var partTime: Int?
}

let jsonString = """
{
    "name": "林志林",
    "partTime": 20
}
"""

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

if let data = jsonData{
    let jsonDecoder = JSONDecoder()
    let t = try jsonDecoder.decode(CPersion.self, from: data)
    let t1 = try jsonDecoder.decode(BPersion.self, from: data)
    print(t)
    print(t1)
}
//result
CPersion(name: "林志林", partTime: Optional(20))
BPersion(name: "林志林", partTime: Optional(20))

数据和对象存在结构差异

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

struct CPerson: 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": "",
    "item.0": "",
    "item.2": "",
    "item.1": ""
}
"""

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

if let data = jsonData {
    let jsonDecoder = JSONDecoder()
    let t = try jsonDecoder.decode(CPerson.self, from: data)
    print(t)
}

//CPerson(elements: ["压", "鹰", "靠", "嗷"])

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

源码解析

上面我们介绍了Codable的基本用法,下面我们看看Codable的源码实现: 上面说到了,Codable定义:包含EncodableDecodable

public typealias Codable = Decodable & Encodable

Decodable

Decodable:解码,用于弱类型数据向自定义类型的转换 一个自定义的类型,只要实现init(from decoder: Decoder) throws,就可以解析。

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

Decoder

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

这里大体看就是JSONDecoder是Decoder的具体实现,它包含各种设置和操作。 我们一个个来看

DateDecodingStrategy

JSONDecoder类定义了DateDecodingStrategy枚举类型,返回何种策略的日期格式,案例如下:

let jsonDecoder = JSONDecoder()
    //deferredToDate:默认策略
    jsonDecoder.dateDecodingStrategy = .deferredToDate
    print("deferredToDate=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
    
    //secondsSince1970:距离1970.01.01的秒数
    jsonDecoder.dateDecodingStrategy = .secondsSince1970
    print("secondsSince1970=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
    
    
    //距离1970.01.01的毫秒数
    jsonDecoder.dateDecodingStrategy = .millisecondsSince1970
    print("millisecondsSince1970=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
    

    //解码为ISO-8601格式(RFC 3339格式)
    jsonDecoder.dateDecodingStrategy = .iso8601
    print("iso8601=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
    
    
    //后台自定义的格式,使用DateFormatter解析
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy年MM月dd日 时间HH:mm:ss"
    jsonDecoder.dateDecodingStrategy = .formatted(formatter)
    print("formatted=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
    
    
    //自定义格式,通过闭包表达式返回Date类型
    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
    }
    print("custom=====>\(try jsonDecoder.decode(CPerson.self, from: data))")

上面根据服务器的数据类型,来设置相应的时间格式

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
}
  1. 入参的泛型T必须遵循Decodable协议
  2. 使用JSONSerializationdata数据序列化为字典的KeyValue
  3. 调用内部类_JSONDecoder传入字典和编码策略返回decoder对象
  4. 通过decoder对象的unbox方法解码并返回value

我们看看内部类_JSONDecoder这里很重要

_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

我们先看decode方法里面调用的构造器方法: 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方法存储要解码的数据container
  • 初始化 options 和 codingPath(空数组)

_JSONDecodingStorage

_JSONDecodingStorage是一个结构体,内部有Any类型数组可存放任意类型,提供pushpopContainer等方法,相当于一个栈容器,它是管理我们传入的container的,后面讲 container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key>再细说

我们继续看unbox干了什么

unbox

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
}

这里面有不同的unbox分支,会根据T的不同,做相应的操作。 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 }
}

查看针对_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类型的解码过程,其实就是一个递归操作,这个是对于dic的解码 其他的类似

我们这里主要研究分支的最后一段

 else {
        self.storage.push(container: value)
        defer { self.storage.popContainer() }
        return try type.init(from: self)
    }

源码中type.init(from:)方法,传入的self,本质是_JSONDecoder type就是我们要解析的value的类型,结合上面的流程,那么init(from:)即应该是我们Decodable协议中的init方法

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

那这里有个疑问。我们前面都是只继承了Decodable,好像从来没有实现过 init(from decoder: Decoder) throws它在哪里实现的呢? 使用命令 swiftc -emit-sil main.swift | xcrun swift-demangle 我们看只继承了Decodable的数据结构,它的SIL是什么样子的。

struct CPerson: Decodable{
    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(CPerson.self, from: jsonData!)
print("-----end")

上面是一个简单的Decoder解码

import Foundation
struct CPerson : Decodable {

  @_hasStorage var name: String { get set }

  @_hasStorage var age: Int { get set }

  enum CodingKeys : CodingKey {

    case name

    case age

    @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: CPerson.CodingKeys, _ b: CPerson.CodingKeys) -> Bool

    func hash(into hasher: inout Hasher)

    init?(stringValue: String)

    init?(intValue: Int)

    var hashValue: Int { get }

    var intValue: Int? { get }

    var stringValue: String { get }

  }

  init(from decoder: Decoder) throws

  init(name: String, age: Int)

}
  • 编译器自动实现CodingKeys枚举类型,并遵循CodingKey协议。解码过程中会通过CodingKeys找到对应case
  • 编译器自动实现decode解码方法:init(from decoder: Decoder) 这也是 Codable的最方便之处,苹果帮我们实现了一套基本类型的解析方案。

我们再看CPerson.init(from:)

/ CPerson.init(from:)
sil hidden @main.CPerson.init(from: Swift.Decoder) throws -> main.CPerson : $@convention(method) (@in Decoder, @thin CPerson.Type) -> (@owned CPerson, @error Error) {
// %0 "decoder"                                   // users: %69, %49, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin CPerson.Type):
  %2 = alloc_stack $Builtin.Int2                  // users: %70, %27, %5, %78, %52
  %3 = alloc_stack [dynamic_lifetime] $CPerson, 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<CPerson.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("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder // users: %13, %13, %12
  %10 = metatype $@thin CPerson.CodingKeys.Type
  %11 = metatype $@thick CPerson.CodingKeys.Type  // user: %13
  %12 = witness_method $@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") 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("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, CPerson.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方法并调用

是不是就是干下面的活儿呢?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        age = try container.decode(Int.self, forKey: .age)
        name = try container.decode(String.self, forKey: .name)
    }

这个很熟悉吧。

KeyedDecodingContainer

所以这里我们再看看container,以及container.decode,decoder.container 再看看Decodable协议,Decoder协议中存在container方法的声明 image.png 查看源码中_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控制,相当于模板文件 image.png 没有以%开始和结束,视为文本直接输出。 这里就不细研究了。

当自己实现init(from decoder: Decoder)方法时,其中decodeCodingKeys都由系统自动生成,所以我们就可以自己重新定义CodingKeys来解决数据和对象存在结构差异的情况。 也可以重新实现init(from decoder: Decoder)方法。

Decodable总结

这里有张图,作为总结我觉得都挺清晰的

image.png

Encodable

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

public protocol Encodable {

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}

Encoder

Encodable 提供的方法 func encode(to encoder: Encoder) throws 里面的Encoder也是一个协议,

public protocol Encoder {

    /// The path of coding keys taken to get to this point in encoding.
    var codingPath: [CodingKey] { get }

    /// Any contextual information set by the user for encoding.
    var userInfo: [CodingUserInfoKey : Any] { get }

    /// Returns an encoding container appropriate for holding multiple values
    /// keyed by the given key type.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - parameter type: The key type to use for the container.
    /// - returns: A new keyed encoding container.
    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey

    /// Returns an encoding container appropriate for holding multiple unkeyed
    /// values.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `container(keyedBy:)` or after
    /// encoding a value through a call to `singleValueContainer()`
    ///
    /// - returns: A new empty unkeyed container.
    func unkeyedContainer() -> UnkeyedEncodingContainer

    /// Returns an encoding container appropriate for holding a single primitive
    /// value.
    ///
    /// You must use only one kind of top-level encoding container. This method
    /// must not be called after a call to `unkeyedContainer()` or
    /// `container(keyedBy:)`, or after encoding a value through a call to
    /// `singleValueContainer()`
    ///
    /// - returns: A new empty single value container.
    func singleValueContainer() -> SingleValueEncodingContainer
}

看这个🌰

struct CPerson: Encodable {
    var name: String
    var age: Int
}

let value = CPerson(name: "liu", age: 99)
let jsonEncoder = JSONEncoder()
let data = try? jsonEncoder.encode(value)
let str = String(data: data!, encoding: .utf8)
print(str)

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

这个流程刚好与Decoder是相反的

  • 创建内部类_JSONEncoder
  • 调用box_方法包装成字典类型
  • 使用JSONSerialization序列化为Data数据

我们先看_JSONEncoder

_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
    }
    
    fileprivate func box_(_ value: Encodable) throws -> NSObject? {
    	......
        do {
        	//调用Encodable的方法
            try value.encode(to: self)
        }
       	......
    }
}

box_

box_方法,根据value的不同类型,调用不同的代码分支,将value包装成对应的数据类型。 下面代码,如果value不是上述定义的数据类型,例如CPerson,最终会调用value.encode(to: self)方法,传入的self就是_JSONEncoder

return try box(value as! [String : Encodable])

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

container方法提供KeyedEncodingContainer

KeyedEncodingContainer

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
}

Encodable总结

image.png 这里分析的比较初略,因为基本思想和Decodable是一样的

踩坑记

Codable不能继承

这个最常见,前面使用中我们也提到过,父类继承Codable,子类是不能完全继承的,也就是说,子类的新元素不能够被Encode或者Decode,很简单,因为

  • 父类遵循了Codable协议,所以系统针对父类自动生成了encode(to encoder: Encoder)方法
  • 子类虽然继承自父类,但并没有重写encode(to encoder: Encoder)方法
  • 所以在编码过程中,找到的依然是父类的encode方法,最终仅父类属性可以被成功编码 通过SIL可以看得很清楚
class CPerson : 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: CPerson.CodingKeys, _ b: CPerson.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
}

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

看他的子类CSuperMan

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

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

class CSuperMan: CPerson {
    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)
    }
    
     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)
    }
    
}

注意try super.encode(to: encoder) 如果在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}}

多态模式下的编解码问题

当结构体存储自定义协议,即使协议遵循Codable协议,依然编译报错,提示:协议类型不符合Decodable协议,只允许使用structenumclass image.png 这时可以考虑类型擦除使用中间层来解决问题

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

struct SuperPerson: BaseInfo {
    var name: String
    var age: Int
}

struct HeroPerson: BaseInfo {
    var name: String
    var age: Int
}

struct PersonBox: BaseInfo, Codable {
    var name: String
    var age: Int
    init(_ baseinfo:BaseInfo) throws {
        self.name = baseinfo.name
        self.age = baseinfo.age
    }
}

struct Group: Codable {
    var groupName: String
    var person:[PersonBox]
}

let person: [BaseInfo] = [SuperPerson(name: "蜘蛛侠", age: 99), HeroPerson(name: "悟空", age: 500)]
let array = try person.map(PersonBox.init)
let aGroup = Group(groupName: "Axiaozu", person: array)

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

let jsonData = try jsonEncoder.encode(aGroup)
if let jsonString = String(data: jsonData, encoding: .utf8) {
    print("编码:\(jsonString)")
}

print("\n--------------------\n")

let jsonDecoder = JSONDecoder()
let c = try? jsonDecoder.decode(Group.self, from: jsonData)
print("解码:\(c)")

///result
编码:{
  "groupName" : "Axiaozu",
  "person" : [
    {
      "name" : "蜘蛛侠",
      "age" : 99
    },
    {
      "name" : "悟空",
      "age" : 500
    }
  ]
}

--------------------

解码:Optional(LGMirror.Group(groupName: "Axiaozu", person: [LGMirror.PersonBox(name: "蜘蛛侠", age: 99), LGMirror.PersonBox(name: "悟空", age: 500)]))

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

方案一 使用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"))

和其他编解码类库的对比

  • SwiftyJSON:使用下标的方式取值,很容易数组越界
  • ObjectMapper:手动对每一个对象提供映射关系,代码量很大
  • HandyJSON:使用内存赋值的方式进行编解码操作,原理和Codable殊途同归
  • Codable:遇到继承和多态模式,需要手动实现编解码功能 以上类库相比较而言,HandyJSONCodable更具优势