一、Codable

Codable 是在 Swift4.0 时开端被引入的新特性,类似 Objective-c 的 NSCoding 和 NSSecureCoding,Codable 的定义如下:

typealias Codable = Decodable & Encodable

它是 Decodable 和 Encodable 协议的类型别名。当 Codable 用作类型或泛型约束时,它匹配符合这两种协议的任何类型。而 Decodable 和 Encodable 是协议类型,通过称谓可以看出来,它们是用作编码和解码的,所以其实和 Objective-c 的 NSCoding 和 NSSecureCoding 功用类似。

Decodable 和 Encodable 的定义如下:

/// A type that can encode itself to an external representation.
public protocol Encodable {
    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}
/// A type that can decode itself from an external representation.
public protocol Decodable {
    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    init(from decoder: Decoder) throws
}

假定你的类型只需单独支撑编码或许解码的,只需求遵循 Decodable 或 Encodable 协议。要一同支撑编码和解码,可以直接遵循 Codable 协议。 在 Swift 标准库中,许多类型都遵循 Codable 协议,例如:String、Int、Double、Date、Data、Array、Dictionary 以及 Optional。

1. 自动编码和解码

下面是一个标明地标名和创建年份的类型:Landmark。

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    // Landmark now supports the Codable methods init(from:) and encode(to:),
    // even though they aren't written as part of its declaration.
}

只需它遵循 Codable 协议,就标明默许支撑编码和解码。假定 Landmark 内嵌一个类型,这个类型也有必要遵循 Codable 协议,才支撑一同编码和解码,例如:

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}
struct Landmark: Codable {
    // Double, String, and Int all conform to Codable.
    var name: String
    var foundingYear: Int
    // Adding a property of a custom Codable type maintains overall Codable conformance.
    var location: Coordinate
}

在前面也说过,Array、Dictionary 以及 Optional 相同也都遵循了 Codable 协议,所以当有相关的内嵌类型的时分,我们可以这么写:

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    // Landmark is still codable after adding these properties.
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
}

单独的编码或许解码这儿就不举例了,我们直接来看下一个东西:CodingKeys。

2. CodingKeys

Codable 类型可以声明一个名为 CodingKeys 的特别嵌套枚举,该枚举符合 CodingKey 协议。当存在此枚举时,其案例将用作可编码类型的实例编码或解码时有必要包括的特色的权威列表。枚举案例的称谓应与您为类型中的相应特色指定的称谓相匹配。

假定解码实例时不存在特色,或许某些特色不该包括在编码标明中,请从 CodingKeys 枚举中省掉特色。CodingKeys 中省掉的特色需求一个默许值,以便其包括类型自动符合 Decodable 或 Codable。

假定序列化数据格式中运用的键与数据类型中的特色称谓不匹配,请通过指定 String 作为 CodingKeys 枚举的原始值类型来供应替代键。作为每个枚举案例的原始值运用的字符串是编码和解码期间运用的密钥名。

上面这段话虽然有点长,但我是从官方的文档抄过来然后翻译的。在开发的时分会常常与后台交互,但往往后台回来的字段姓名有些是非常奇怪的,一个是命名规矩,一个是命名的含义。假定不想和后台的命名共同,希望自己运用的时分愈加直观,我们就可以运用 CodingKeys 来处理。

官方的比方如下:

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    var vantagePoints: [Coordinate]
    enum CodingKeys: String, CodingKey {
        case name = "title"
        case foundingYear = "founding_date"
        case location
        case vantagePoints
    }
}

通过 CodingKeys 枚举就可以将 JSON 中的 title 和 founding_date 字段替换成我们自定义的 name 和 foundingYear。

后面的 location 和 vantagePoints 为什么要写呢,是因为假定序列化数据格式中运用的键与数据类型中的特色称谓不匹配,需求指定 String 作为枚举的原始值,不然编译器会报错的。

例如上面的比方,运用 CodingKeys 改变编码特色的称谓后:

Landmark 解码的成果为:

{"name":"xxx","foundingYear":xxx, location:xxx, vantagePoints:xxx}

Landmark 编码的成果为:

{"title":"xxx","founding_date":xxx,location:xxx, vantagePoints:xxx}

3. 手动编码和解码

前面现已了解了自动编码和解码,那么其实我们也可以自己手动的去编码和解码。我们引入一个官方的比方:

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double
    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }
    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

第一次看到这个比方你或许会有点懵,不知道 AdditionalInfoKeys 枚举是用来干什么的。先来看一段 JSON 的结构:

{
    "latitude" : xxx
    "longitude": xxx
    "additionalInfo": {
        "elevation" : xxx
        "elevation2": xxx
        "elevation3": xxx
        ......
    }
}

这段 JSON 数据最外层的有三个字段,latitude、longitude 以及 additionalInfo,并且 additionalInfo 内部还有字段,所以 Coordinate 内部的 CodingKeys 的结构是这样的:

enum CodingKeys: String, CodingKey {
    case latitude
    case longitude
    case additionalInfo
}

这个时分你应该对 CodingKeys 有一个更明晰的理解了,CodingKeys 本质上是用来描绘 JSON 中的 key 的。那么关于 additionalInfo 来说,我们或许只需求其间的 elevation,所以就有了 AdditionalInfoKeys:

enum AdditionalInfoKeys: String, CodingKey {
    case elevation
}

所以,可以认为,AdditionalInfoKeys 就是用来 additionalInfo 内部的 key 的。此时在 Coordinate 中,我们就可以直接用 additionalInfo 中的 elevation 作为 Coordinate 的特色。

在了解 Coordinate 内部构成的含义之后,我们来看一下怎样手动的编码和解码。首要来看解码:

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}

首要运用 extension 对 Coordinate 进行扩展并且遵循 Decodable 协议,然后完结协议的 init(from:) 方法,我们来看一下 init(from:) 中运用的 API 的含义。

  • container(keyedBy:):获取 CodingKey 的对应联系。

  • decode(:forKey:):解析单个特色。

  • nestedContainer(keyedBy:forKey:):获取内嵌的层级。

接下来看一下手动编码:

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
}

其实也很简略,相关于手动解码,只是调用的方法不相同,但这个进程、过程是相同的。

二、编码器与解码器

通过了解 Codable 我们现已知道,只需遵循了 Codable 协议,这个类型就支撑编码和解码。而 JSONEncoder 和 JSONDecoder 就是用来进行编码和解码的。它们都是一个 class 类型。

1. JSONDecoder

现在看一下 JSONDecoder 是怎样解码的,取的也是一个官方的比方:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}
let json = """
{
"name": "Durian",
"points": 600,
"description": "A fruit with a distinctive scent."
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)
print(product.name) // Prints "Durian"

通过生成 JSONDecoder 方针,调用方针的 decode(:from:) 方法来进行解码,需求留心的是第一个参数传的是支撑 Decodable 协议的类型,第二个传的是一个 Data 类型,这个和我们平常用的第三方字典转模型不太相同。

2. JSONEncoder

运用 JSONEncoder 方针进行编码也是非常简略的,相同是官方的比方:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
    "name" : "Pear",
    "points" : 250,
    "description" : "A ripe pear."
}
*/

三、源码浅析

了解 Codable 以及 JSONDecoder、JSONEncoder 之后,接下来通过源码浅浅的分析一下它们的作业原理。现在最新的 Swift 源码时不包括 JSONDecoder、JSONEncoder 的源码的,只要 Codable 源码,Apple 把这两个类的源码放在另一个工程:swift-corelibs-foundation。

所以接下来需求用到两份源码: swift-corelibs-foundation,swift 源码。

1. JSONDecoder 源码浅析

1.1 JSONParser 与 JSONValue

首要打开 swift-corelibs-foundation 的源码,找到 JSONDecoder.swift 文件,先来看 decode(:from:) 方法的完结:

open func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
    do {
        // 生成 JSON 解析器,内部会用一个 Array 来存储 data
        var parser = JSONParser(bytes: Array(data))
        // 开端解析,生成 JSONValue
        let json = try parser.parse()
        // 生成 JSONDecoderImpl,由 JSONDecoderImpl 将 JSONValue 转成 Decodable 方针。
        return try JSONDecoderImpl(userInfo: self.userInfo, from: json, codingPath: [], options: self.options).unwrap(as: T.self)
    } catch let error as JSONError {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
    } catch {
        throw error
    }
}

通过源码得知,开发者传入的 data 会被包装成 JSONParser 解析器,通过 JSONParser 对 JSON 数据进行解析,它的初始化方法非常简略:

init(bytes: [UInt8]) {
    self.reader = DocumentReader(array: bytes)
}

DocumentReader 自身是对 bytes 的一个操作,我们直接看 JSONParser 的 parse 方法:

mutating func parse() throws -> JSONValue {
    try reader.consumeWhitespace()
    let value = try self.parseValue()
#if DEBUG
    defer {
        guard self.depth == 0 else {
        preconditionFailure("Expected to end parsing with a depth of 0")
        }
    }
#endif
    // ensure only white space is remaining
    var whitespace = 0
    while let next = reader.peek(offset: whitespace) {
        switch next {
        case ._space, ._tab, ._return, ._newline:
            whitespace += 1
            continue
            default:
            throw JSONError.unexpectedCharacter(ascii: next, characterIndex: reader.readerIndex + whitespace)
        }
    }
    return value
}

这儿调用 DocumentReader 的 consumeWhitespace,consumeWhitespace 方法的完结如下:

mutating func moveReaderIndex(forwardBy offset: Int) {
    self.readerIndex += offset
}
mutating func consumeWhitespace() throws -> UInt8 {
    var whitespace = 0
    while let ascii = self.peek(offset: whitespace) {
        switch ascii {
        case ._space, ._return, ._newline, ._tab:
            whitespace += 1
            continue
            default:
            self.moveReaderIndex(forwardBy: whitespace)
            return ascii
        }
    }
    throw JSONError.unexpectedEndOfFile
}

我们再来看一下 ._space, ._return, ._newline, ._tab 都是些什么:

internal static let _space = UInt8(ascii: " ")
internal static let _return = UInt8(ascii: "\r")
internal static let _newline = UInt8(ascii: "\n")
internal static let _tab = UInt8(ascii: "\t")

所以,consumeWhitespace 其实是将空格、换行等对应的字符做一个过滤,调用 moveReaderIndex 方法拿到非将空格、换行的第一个 readerIndex。然后调用 parseValue 方法将 bytes 解析成 JSONValue。

JSONValue 是一个枚举,可以用来标明 String、Bool、数组、字典等类型:

enum JSONValue: Equatable {
    case string(String)
    case number(String)
    case bool(Bool)
    case null
    case array([JSONValue])
    case object([String: JSONValue])
}

此时再来看 parseValue 方法的完结:

mutating func parseValue() throws -> JSONValue {
    var whitespace = 0
    while let byte = reader.peek(offset: whitespace) {
        switch byte {
        case UInt8(ascii: "\""):
            reader.moveReaderIndex(forwardBy: whitespace)
            return .string(try reader.readString())
        case ._openbrace:
            reader.moveReaderIndex(forwardBy: whitespace)
            let object = try parseObject()
            return .object(object)
        case ._openbracket:
            reader.moveReaderIndex(forwardBy: whitespace)
            let array = try parseArray()
            return .array(array)
        case UInt8(ascii: "f"), UInt8(ascii: "t"):
            reader.moveReaderIndex(forwardBy: whitespace)
            let bool = try reader.readBool()
            return .bool(bool)
        case UInt8(ascii: "n"):
            reader.moveReaderIndex(forwardBy: whitespace)
            try reader.readNull()
            return .null
        case UInt8(ascii: "-"), UInt8(ascii: "0") ... UInt8(ascii: "9"):
            reader.moveReaderIndex(forwardBy: whitespace)
            let number = try self.reader.readNumber()
            return .number(number)
        case ._space, ._return, ._newline, ._tab:
            whitespace += 1
            continue
            default:
            throw JSONError.unexpectedCharacter(ascii: byte, characterIndex: self.reader.readerIndex)
        }
    }
    throw JSONError.unexpectedEndOfFile
}

在这个方法傍边调用了许多的方法对 bytes 进行解析,例如:reader.readString()、reader.readBool()、parseArray() 等,代码太多了,我也大约看了一下,看不懂。

但此时我们应该知道,JSONDecoder 内部并不会通过 JSONSerialization 对 data 进行反序列化的操作。在 parse 方法中有一个 while 循环,这个不重要,到这儿 parse 方法的浅析就结束了。

持续回到 decode(:from:) 方法,解析生成 JSONValue 之后,由 JSONDecoderImpl 来转成开发者需求的 Decodable 方针。

1.2 JSONDecoderImpl

JSONDecoderImpl 的定义如下:

fileprivate struct JSONDecoderImpl {
    let codingPath: [CodingKey]
    let userInfo: [CodingUserInfoKey: Any]
    let json: JSONValue
    let options: JSONDecoder._Options
    init(userInfo: [CodingUserInfoKey: Any], from json: JSONValue, codingPath: [CodingKey], options: JSONDecoder._Options) {
        self.userInfo = userInfo
        self.codingPath = codingPath
        self.json = json
        self.options = options
    }
}

可以看到,它的初始化方法非常的简略,只是记录一下外部的参数,留心!JSONDecoderImpl 是有遵循 Decoder 协议的,这点必定要留心,在后面的会有解说。

我们直接看到 unwrap(:) 方法:

func unwrap<T: Decodable>(as type: T.Type) throws -> T {
    if type == Date.self {
        return try self.unwrapDate() as! T
    }
    if type == Data.self {
        return try self.unwrapData() as! T
    }
    if type == URL.self {
        return try self.unwrapURL() as! T
    }
    if type == Decimal.self {
        return try self.unwrapDecimal() as! T
    }
    if T.self is _JSONStringDictionaryDecodableMarker.Type {
        return try self.unwrapDictionary(as: T.self)
    }
    return try T(from: self)
}

在这个方法中,无非就是根据 T.Type 来针对转成相应的类型做的处理,至于 unwrapDate、unwrapData 等方法这儿就不去看了,我们直接看到毕竟一个回来的调用:

return try T(from: self)

到这儿,JSONDecoder 的源码浅析就结束了,在毕竟调用的方法其实是 Decodable 的 init(from:) 方法:

init(from decoder: Decoder) throws

2. Decodable 源码浅析

通过对 JSONDecoder 源码的浅析,现已得知,除了几个特别的类型外,毕竟走的都是遵循 Decodable 的 init(from:) 方法,那在 swift 的 Decodable 源码中,是没有针对 T(from: self) 的完结的。所以,我们只能通过底层的 sil 的代码去窥探 T(from: self) 的完结。

假定不了解 sil 的可以通过《方法》 这篇文章去了解。代码如下:

struct Person: Decodable {
    var nickname: String
    var age: Int
}

生成 sil 之后直接找到 Person.init(from:) 的完结,一般来讲这个初始化方法上方都会有一个注释奉告你该方法的称谓,例如:

// Person.init(from:)
sil hidden @$s4main6PersonV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Person.Type) -> (@owned Person, @error Error) {
......

因为代码过长,就不一一的贴出来了,许多代码我都是直接疏忽的,我们直接看要害的,如下:

// Person.init(from:)
sil hidden @$s4main6PersonV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin Person.Type) -> (@owned Person, @error Error) {
// %0 "decoder"                                   // users: %69, %49, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin Person.Type):
......
 %8 = alloc_stack $KeyedDecodingContainer<Person.CodingKeys>, let, name "container", implicit // users: %45, %44, %37, %66, %65, %21, %60, %59, %13, %55
 %9 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder // users: %13, %13, %12
 %10 = metatype $@thin Person.CodingKeys.Type
 %11 = metatype $@thick Person.CodingKeys.Type   // user: %13
 %12 = witness_method $@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") 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("FC2B369C-D5BE-11EC-BBC1-AAEFB94EC64D") Decoder, Person.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
}

先来看第一段代码 bb0,这一段代码第一次看的时分或许会有点懵逼,留心看要害的部分:%12 这行,我们看到 witness_method,假定看过我写的《协议的本质》 这篇文章,这其实是一个协议见证表的调度方法。

也就意味着 bb0 中首要调用的是 Decoder 协议的方法,也就是这个方法:

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey

那这个方法是完结在哪里呢,还记得前面讲过 JSONDecoderImpl 是有遵循 Decoder 协议的么,在 JSONDecoderImpl 的 unwrap(:) 方法的毕竟一个回来值

return try T(from: self)

这个 self 不正是 JSONDecoderImpl 么,所以在 Person.init(from:) 中的 Decoder 参数,本质上就是 JSONDecoderImpl。

那么在 JSONDecoderImpl 中 container<Key>(keyedBy:) 方法的完结如下:

@usableFromInline func container<Key>(keyedBy _: Key.Type) throws ->
    KeyedDecodingContainer<Key> where Key: CodingKey
{
    guard case .object(let dictionary) = self.json else {
        throw DecodingError.typeMismatch([String: JSONValue].self, DecodingError.Context(
            codingPath: self.codingPath,
            debugDescription: "Expected to decode \([String: JSONValue].self) but found \(self.json.debugDataTypeDescription) instead."
        ))
    }
    let container = KeyedContainer<Key>(
        impl: self,
        codingPath: codingPath,
        dictionary: dictionary
    )
    return KeyedDecodingContainer(container)
}

所以 Person.init(from:) 方法中的第一行代码我们应该搞定了,不就是调用了 container<Key>(keyedBy:) 方法么,完结如下:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    ......
}

此时,留心看 bb0 的毕竟一行,假定成功调用的是 bb1,我们接着看 bb1 的代码:

bb1(%14 : $()):                                   // Preds: bb0
  %15 = metatype $@thin String.Type               // user: %21
  %16 = metatype $@thin Person.CodingKeys.Type
  %17 = enum $Person.CodingKeys, #Person.CodingKeys.nickname!enumelt // user: %19
  %18 = alloc_stack $Person.CodingKeys            // users: %19, %23, %21, %58
  store %17 to %18 : $*Person.CodingKeys          // id: %19
  // function_ref KeyedDecodingContainer.decode(_:forKey:)
  %20 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2Sm_xtKF : $@convention(method) <_0_0 where _0_0 : CodingKey> (@thin String.Type, @in_guaranteed _0_0, @in_guaranteed KeyedDecodingContainer<_0_0>) -> (@owned String, @error Error) // user: %21
  try_apply %20<Person.CodingKeys>(%15, %18, %8) : $@convention(method) <_0_0 where _0_0 : CodingKey> (@thin String.Type, @in_guaranteed _0_0, @in_guaranteed KeyedDecodingContainer<_0_0>) -> (@owned String, @error Error), normal bb2, error bb5 // id: %21

这一段比较简略,人家连注释都给你了,不就是调用 KeyedDecodingContainer 的 decode(_:forKey:) 方法么,并且成功之后跳转到 bb2,我们再来看 bb2 的完结:

// %22                                            // users: %63, %48, %46, %29, %28
bb2(%22 : $String):                               // Preds: bb1
  dealloc_stack %18 : $*Person.CodingKeys         // id: %23
  %24 = begin_access [modify] [static] %3 : $*Person // users: %30, %25
  %25 = struct_element_addr %24 : $*Person, #Person.nickname // user: %29
  %26 = integer_literal $Builtin.Int2, 1          // user: %27
  store %26 to %2 : $*Builtin.Int2                // id: %27
  retain_value %22 : $String                      // id: %28
  store %22 to %25 : $*String                     // id: %29
  end_access %24 : $*Person                       // id: %30
  %31 = metatype $@thin Int.Type                  // user: %37
  %32 = metatype $@thin Person.CodingKeys.Type
  %33 = enum $Person.CodingKeys, #Person.CodingKeys.age!enumelt // user: %35
  %34 = alloc_stack $Person.CodingKeys            // users: %35, %39, %37, %64
  store %33 to %34 : $*Person.CodingKeys          // id: %35
  // function_ref KeyedDecodingContainer.decode(_:forKey:)
  %36 = function_ref @$ss22KeyedDecodingContainerV6decode_6forKeyS2im_xtKF : $@convention(method) <_0_0 where _0_0 : CodingKey> (@thin Int.Type, @in_guaranteed _0_0, @in_guaranteed KeyedDecodingContainer<_0_0>) -> (Int, @error Error) // user: %37
  try_apply %36<Person.CodingKeys>(%31, %34, %8) : $@convention(method) <_0_0 where _0_0 : CodingKey> (@thin Int.Type, @in_guaranteed _0_0, @in_guaranteed KeyedDecodingContainer<_0_0>) -> (Int, @error Error), normal bb3, error bb6 // id: %37

和 bb1 底子是相同的,毕竟也是调用 KeyedDecodingContainer 的 decode(_:forKey:) 方法,并且成功之后跳转到 bb3,bb3 我们就不去看了,看到这儿现已够了。

此时我们底子可以直接得出 Person.init(from:) 的完结,大约完结如下:

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

当然,你要复制到自己的代码肯定是会报错的,因为这是底层的完结,报错了自己改改就行,此时就会发现这儿和前面的手动编码是相同的。

3. KeyedDecodingContainer 源码浅析

KeyedDecodingContainer 是一种详细的容器,供应解码器存储器的视图,使可解码类型的编码特色可由键访问。其的定义如下:

/// A concrete container that provides a view into a decoder's storage, making
/// the encoded properties of a decodable type accessible by keys.
public struct KeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K : CodingKey {
// 更多完结
......

留心看,KeyedDecodingContainer 遵循 KeyedDecodingContainerProtocol 协议,KeyedDecodingContainerProtocol 中声明晰非常多的计算特色和方法,而方法的完结是由 KeyedDecodingContainer 完结的,内部是怎样完结的呢。

这儿拿其间的一个方法做比方:

public func decode<T: Decodable>(
_ type: T.Type,
forKey key: Key
) throws -> T {
    return try _box.decode(T.self, forKey: key)
}

可以看到,内部通过一个 _box 特色调用了 decode<T>(:forKey) 方法,接着来看 _box 是什么:

/// The container for the concrete decoder.
internal var _box: _KeyedDecodingContainerBase

这是一个用来描绘解码器的容器,在源码中找到 _KeyedDecodingContainerBase,发现它只是一个基类,真正的完结是由子类完结的,其子类如下:

internal final class _KeyedDecodingContainerBox<
Concrete: KeyedDecodingContainerProtocol
>: _KeyedDecodingContainerBase {
    typealias Key = Concrete.Key
    internal var concrete: Concrete
    internal init(_ container: Concrete) {
        concrete = container
    }
// 更多完结
......

留心看这个 Concrete,Concrete 遵循 KeyedDecodingContainerProtocol 协议,这个地方或许有点绕。我们回到 KeyedDecodingContainer,先来看看 KeyedDecodingContainer 的初始化方法:

/// Creates a new instance with the given container.
///
/// - parameter container: The container to hold.
public init<Container: KeyedDecodingContainerProtocol>(
_ container: Container
) where Container.Key == Key {
    _box = _KeyedDecodingContainerBox(container)
}

可以看到这个初始化方法要求传一个遵循 KeyedDecodingContainerProtocol 协议的 Container 类型,然后再传给 _KeyedDecodingContainerBox。那这个 Container 其实在是在 JSONDecoderImpl 中的 container<Key>(keyedBy:) 方法

在 JSONDecoderImpl 中 container<Key>(keyedBy:) 方法有说到,Container 本质上是一个 KeyedContainer<K: CodingKey> 结构体,其定义如下:

struct KeyedContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
typealias Key = K
// 更多完结
......

在这儿不要被弄晕了,虽然在两份源码之间跳来跳去,但细心阅读的时分仍是能理解的。

我们接着回到 _KeyedDecodingContainerBox,来看 decode<T>(:forKey) 在 _KeyedDecodingContainerBox 中是怎样完结的:

override internal func decode<T: Decodable, K: CodingKey>(
_ type: T.Type,
forKey key: K
) throws -> T {
    _internalInvariant(K.self == Key.self)
    let key = unsafeBitCast(key, to: Key.self)
    return try concrete.decode(T.self, forKey: key)
}

此时你会发现,在做一个类型的强制转化之后,调用的是 concrete 的 decode<T>(:forKey) 方法,而这个 concrete 不正是前面讲的遵循 KeyedDecodingContainerProtocol 协议的 KeyedContainer 类型吗,此时我们来看看 KeyedContainer 又是怎样完结 decode<T>(:forKey) 方法的:

func decode<T>(_: T.Type, forKey key: K) throws -> T where T: Decodable {
    let newDecoder = try decoderForKey(key)
    return try newDecoder.unwrap(as: T.self)
}

首要拿到一个新的解码器,通过解码器调用 unwrap(as:) 方法进行解码,怎样获得解码器的呢?持续看 decoderForKey(:) 方法的完结:

private func decoderForKey<LocalKey: CodingKey>(_ key: LocalKey) throws -> JSONDecoderImpl {
    let value = try getValue(forKey: key)
    var newPath = self.codingPath
    newPath.append(key)
    return JSONDecoderImpl(
           userInfo: self.impl.userInfo,
           from: value,
           codingPath: newPath,
           options: self.impl.options
           )
}
@inline(__always) private func getValue<LocalKey: CodingKey>(forKey key: LocalKey) throws -> JSONValue {
    guard let value = dictionary[key.stringValue] else {
        throw DecodingError.keyNotFound(key, .init(
        codingPath: self.codingPath,
        debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\")."
        ))
    }
    return value
}

所以毕竟又回到了 JSONDecoderImpl,所以,JSONDecoder 去解析一个非标准库中遵循 Decodable 协议类型的时分,大致的流程底子就是这样的。

毕竟附上一张流程图:

iOS-Swift 独孤九剑:十四、Codable 的根本应用及源码浅析

四、解码容器

  • KeyedDecodingContainer:供应解码器存储视图的详细容器,使可解码类型的编码特色可通过键访问。

  • UnkeyedDecodingContainer:一种供应解码器存储视图的类型,用于按次序保存可解码类型的编码特色,无需密钥。解码器应为其格式供应符合 UnkeyedDecodingContainer 的类型。

  • SingleValueDecodingContainer:一个容器,可以支撑单个非键值的存储和直接解码。

文字描绘的差异或许有点干,下面通过一段伪代码来描绘:

// KeyedDecodingContainer,通过字典key来取值
let value = container[key]
// UnkeyedDecodingContainer,通过数组下标来取值
let value = container[index]
// SingleValueDecodingContainer,value 就是 container 自身
let value = container

1. KeyedDecodingContainer 运用示例

struct Person: Decodable {
    var nickname: String
    var age: Int
    private enum CodingKeys: String, CodingKey {
        case nickname = "name"
        case age
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        nickname = try container.decode(String.self, forKey: .nickname)
        age = try container.decode(Int.self, forKey: .age)
    }
}
let jsonString = """
{
"name": "Tom",
"age": 23,
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let jsonData = jsonData,
let result = try? decoder.decode(Person.self, from: jsonData) {
    print(result)
} else {
    print("解析失利")
}

当 jsonString 能被序列化成字典的时分,就应该挑选 KeyedDecodingContainer 作为 解码容器。

2. UnkeyedDecodingContainer 运用示例

这是一个 Point:

struct Point: Decodable {
    var x: Double
    var y: Double
}

此时服务器回来的 json 数据不是一个字典,而是一个数组:

let jsonString = "[10,20]"

此时就不能运用 KeyedDecodingContainer 去解析了,而是用 UnkeyedDecodingContainer,代码如下:

struct Point: Decodable {
    var x: Double
    var y: Double
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        x = try container.decode(Double.self)
        y = try container.decode(Double.self)
    }
}

假定你的 JSON 数据能被序列化成数组,那么可以用 UnkeyedDecodingContainer 来解析,至于 UnkeyedDecodingContainer 的源码,感兴趣的靓仔靓女们可以参照前面 KeyedDecodingContainer 的源码分析的方法对 UnkeyedDecodingContainer 的源码进行阅读。

3. SingleValueDecodingContainer 运用示例

假定,服务器回来一个字段,或许是 Int 类型,或许是 String 类型,虽然这种状况比较少,但仍是有些后台会这么给数据 。

我们可以自己一同支撑 String 和 Int 的类型:

struct StringInt: Codable {
    var stringValue: String
    var intValue: Int
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(String.self) {
            stringValue = value
            intValue = Int(value) ?? 0
        } else if let value = try? container.decode(Int.self) {
            stringValue = "\(value)"
            intValue = value
        } else {
            stringValue = ""
            intValue = 0
        }
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if !stringValue.isEmpty {
            try? container.encode(stringValue)
        } else {
            try? container.encode(intValue)
        }
    }
}
extension StringInt: ExpressibleByStringLiteral {
    init(stringLiteral value: StringLiteralType) {
        stringValue = value
        intValue = Int(value) ?? 0
    }
    init(unicodeScalarLiteral value: String) {
        stringValue = value
        intValue = Int(value) ?? 0
    }
    init(extendedGraphemeClusterLiteral value: String) {
        stringValue = value
        intValue = Int(value) ?? 0
    }
}
extension StringInt: ExpressibleByIntegerLiteral {
    init(integerLiteral value: IntegerLiteralType) {
        stringValue = "\(value)"
        intValue = value
    }
}

当我们拿到的数据或许是 Int 类型,或许是 String 类型的时分,我们可以通过 SingleValueDecodingContainer 分别检验对 String.self 和 Int.self 进行解析。

五、运用 Property Wrapper 为 Codable 解码设定默许值

当你看到这儿的时分,底子上了解怎样运用 Codable 进行编码和解码了,但其实在开发中我们难免会遇到一些问题,例如服务器回来的某个字段或许会为 nil 或许乃至不给,这个时分我们需求在模型中加上可选项,但在取一个可选值的时分我们需求进行解包,然后就会有非常多的 if else,写起来吃力,看起来难受。

针对这个问题,我自己写一份运用 Property Wrapper 为 Codable 解码设定默许值的代码,如下:

// 具有 DefaultValue 协议和 Codable 协议的组合协议,目的是使 SingleValueDecodingContainer 的 decode 确保语法上正确!
typealias DefaultCodableValue = DefaultValue & Codable
// 特色包装器
@propertyWrapper
struct Default<T: DefaultCodableValue> {
    var wrappedValue: T
}
// 包装器遵循 Codable 协议,完结默许的 decoder 和 encoder 方法
extension Default: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}
// 关于 key 不存在或许意外输入不同类型时运用默许的值
extension KeyedDecodingContainer {
    func decode<T>(
        _ type: Default<T>.Type,
        forKey key: Key
    ) throws -> Default<T> where T: DefaultCodableValue {
        if let value = try decodeIfPresent(type, forKey: key) {
            return value
        } else {
            return Default(wrappedValue: T.defaultValue)
        }
    }
}
// 数组相关的处理,含义和 KeyedDecodingContainer 的处理相同
extension UnkeyedDecodingContainer {
    mutating func decode<T>(
        _ type: Default<T>.Type
    ) throws -> Default<T> where T : DefaultCodableValue {
            try decodeIfPresent(type) ?? Default(wrappedValue: T.defaultValue)
    }
}
// 默许值协议
protocol DefaultValue {
    static var defaultValue: Self { get }
}
// 可以给某个或许为 nil 的类型遵循 DefaultValue,使其具有 defaultValue
extension Bool: DefaultValue { static let defaultValue = false }
extension String: DefaultValue { static var defaultValue = "" }
extension Int: DefaultValue { static var defaultValue = 0 }
extension Double: DefaultValue { static var defaultValue = 0.0 }
extension Float: DefaultValue { static var defaultValue: Float { 0.0 } }
extension Array: DefaultValue { static var defaultValue: Array<Element> { [] } }
extension Dictionary: DefaultValue { static var defaultValue: Dictionary<Key, Value> { [:] } }

在此还要感谢喵神的一篇文章:运用 Property Wrapper 为 Codable 解码设定默许值。