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(keyedBy type: Key.Type) throws -> KeyedDecodingContainer 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..(_ 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(_ 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(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
        guard !(self.storage.topContainer is NSNull) else {
            throw DecodingError.valueNotFound(KeyedDecodingContainer.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(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(_ 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(_ 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, 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) -> (Key.Type) throws -> KeyedDecodingContainer, %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

public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer {
    guard !(self.storage.topContainer is NSNull) else {
        throw DecodingError.valueNotFound(KeyedDecodingContainer.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(referencing: self, wrapping: topContainer)
    return KeyedDecodingContainer(container)
}

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

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

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(keyedBy: Key.Type) -> KeyedEncodingContainer {
        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(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结构体,遵循KeyedEncodingContainerProtocol协议,要求K必须遵循CodingKey协议,内部定义了各种类型对应的encode方法

public struct KeyedEncodingContainer : KeyedEncodingContainerProtocol where K : CodingKey {
    public typealias Key = K
    public init(_ container: Container) where K == Container.Key, Container : KeyedEncodingContainerProtocol
    public var codingPath: [CodingKey] { get }
    public mutating func encodeNil(forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Bool, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: String, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Double, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Float, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Int, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Int8, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Int16, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Int32, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: Int64, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: UInt, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: UInt8, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: UInt16, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: UInt32, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: UInt64, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encode(_ value: T, forKey key: KeyedEncodingContainer.Key) throws where T : Encodable
    public mutating func encodeConditional(_ object: T, forKey key: KeyedEncodingContainer.Key) throws where T : AnyObject, T : Encodable
    public mutating func encodeIfPresent(_ value: Bool?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: String?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Double?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Float?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Int?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Int8?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Int16?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Int32?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: Int64?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: UInt?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: UInt8?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: UInt16?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: UInt32?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: UInt64?, forKey key: KeyedEncodingContainer.Key) throws
    public mutating func encodeIfPresent(_ value: T?, forKey key: KeyedEncodingContainer.Key) throws where T : Encodable
    public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: KeyedEncodingContainer.Key) -> KeyedEncodingContainer where NestedKey : CodingKey
    public mutating func nestedUnkeyedContainer(forKey key: KeyedEncodingContainer.Key) -> UnkeyedEncodingContainer
    public mutating func superEncoder() -> Encoder
    public mutating func superEncoder(forKey key: KeyedEncodingContainer.Key) -> Encoder
    public mutating func encodeConditional(_ 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(_ 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: 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: Codable{
    private var person: [MetaObject]
    
    init() {
        self.person = []
    }
    
    mutating func add(p: LGPerson) {
        person.append(MetaObject(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()
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.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(person: [LGSwiftTest.MetaObject(object: LGSwiftTest.LGTeacher), LGSwiftTest.MetaObject(object: LGSwiftTest.LGPartTimeTeacher)]))
和其他编解码类库的对比
  • SwiftyJSON:使用下标的方式取值,很容易数组越界
  • ObjectMapper:手动对每一个对象提供映射关系,代码量很大
  • HandyJSON:使用内存赋值的方式进行编解码操作,原理和Codable殊途同归
  • Codable:遇到继承和多态模式,需要手动实现编解码功能

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

你可能感兴趣的:(Swift底层进阶--015:Codable源码解析)