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

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更具优势

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容