Codable in Swift

理解代替记忆

什么是Codable

Codable is a type alias

typealias Codable = Decodable & Encodable

  • 在进行网络传输时,经常会将应用中的model转换为网络传输中的数据格式如json,这叫做编码(Encode);那么接收到网络json数据后,将其转为app中的model则是解码(Decode)
  • Swift standard library中定义了几个协议和类来实现编码、解码
    • 协议有Encodable、Decodable、Encoder、Decoder
    • 类有JSONDecoder、JSONEncoder、PropertyListEncoder、PropertyListDecoder

Encodable、Decodable

  • 需要编解码的model需要遵循这两个协议
  • Encodable有一个方法需要实现--func encode(to encoder: Encoder) throws
  • Decodable的方法是--init(from decoder: Decoder) throws

编解码过程

根据JSONDecoder的源码分析可将大致的编解码过程概括为如下:

  1. 从执行JSONDecoder或其他coder的decode/encode方法开始
  2. 根据要编解码的类型,分别执行相应类型所遵循的DecodableEncodableinitencode方法
  3. initencode方法内部,通过DecoderEncoder的三个方法(参考下面Decoder小节),获取到要进行编解码的中间数据结构container
  4. 通过container具体的encode和decode方法将数据从container解码出来,或编码到container中
    • 这一步中的encodedecode方法内部,其实是递归的对model的每个property进行编解码,即执行property类型的initdecode方法对property编解码
    • 每层递归,系统都会根据不同层的CodingKeys数据,对container进行剥离(Decode协议一节有细讲),将剥离后的decoder或encoder,传到下一层的initencode方法中
  5. initencode方法结束,编解码过程也就结束了

系统自动做的工作

为了节约开发时间,编译器会根据情况自动添加编解码的代码

比如当一个数据类型遵循EncodableDecodable时,且该类型的每个property也是codable的,那编译器会:

  1. 自动添加一个名为CodingKeys的实现了CodingKey协议的enum;enmu的每个case与该类型的每个property一一对应,即默认会为每个property都进行编解码
  2. 自动实现了initencode方法

当然,如果不是每个property都是codable的或者我们不想每个property都去编解码,我们也可以自己实现CodingKey,而initencode方法系统仍可以自动实现

  • 这样可以选择对部分属性进行编解码
  • 当remote data的key名称和memory data的property名字不同时,可以通过自定义来匹配名称

支持的数据类型

一句话,支持所有实现DecodableEncodable的数据类型

很多现有数据类型已经支持该协议

StringIntDoubleURLDataDate等等

Decoder协议

Encoder协议也是类似的几个属性和方法

DecoderEncoder出现在前面所说的initencode方法中,它将编解码的数据存放到中间数据结构container中。下面是该协议的属性和方法:

  • var codingPath: [CodingKey] { get }

    • 这个值一般开发者用不到,只有在编解码过程中才会有值,用于编解码时寻找数据的层级
    • 出错后,可以使用该值来记录发生错误的key
  • var userInfo: [CodingUserInfoKey : Any] { get }

    • 不知怎么用
  • func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey

  • func singleValueContainer() throws -> SingleValueDecodingContainer

  • func unkeyedContainer() throws -> UnkeyedDecodingContainer

这三个方法用于从container中取数据或往其中写数据。从decoder的三个方法能够看出(WWDC中也有提到),编解码时支持三种样式数据的编解码。分别是

  • 形如Dictionary的数据,比如常见的最外层是jsonObject的json数据--对应container方法
  • 任意一个支持Decodable的类型,对应singleValueContainer方法
    • 比如下面的json数据是一个表示文本信息的JSONObject,要转成TextInfo的model,其中的color这个key对应一个字符串类型,表示一个颜色值
      {
          "color": "(12, 12, 12)",
          "text" : "I am songgeb!"
      }
      
    • 我们想把这个颜色值解析成一个自定义的颜色类型比如叫做MyColor
    • 一步步来,我们先在TextInfoinit方法中,通过container(keyedBy type: Key.Type)方法获取到container,再执行container的decode方法,指定MyColor类型继续进行解码
    • 然后,就来到了MyColorinit方法中,此时的decoder对应的container是剥离后的,只剩下"(12, 12, 12)"内容。所以可以通过singleValueContainer方法获取到字符串的值,然后就可以进一步处理了
    //TextInfo中
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.text = try container.decode(String.self, forKey: .text)
        self.color = try container.decode(MyColor.self, forKey: .color)
    }
    //MyColor中
    required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let str = try container.decode(String.self)
        ...
    }
    
  • 数组类型(数组的每个元素都是支持Decodable),比如jsonArray数据类型。对应unkeyedContainer方法
    [
        {...}, {...}
    ]
    

CodingKey协议

CodingKey在编解码过程中用于定位要处理的数据

protocol Codingkey {
  var stringValue: String { get }
  var intValue: Int? { get }
  init?(stringValue: String)
  init?(intValue: Int)
}

上面我们提到,编解码时支持三种样式的数据类型,一次编解码中,三种样式数据都可能出现,那么CodingKey便用于定位编解码的数据

  • 通过协议从属性和方法能看出,CodingKey一定会有一个string值,这很容易理解,对于Dictionary样式的数据,可以用这个string值,结合KVC进行赋值和取值
  • intValue则是对应数组类型的数据,因为这种数据没有key,必须通过intValue作为下标,进行取赋值

JSONDecoder和Decoder协议

当前有JSONDecoder/JSONEncoderPlistDecoder/PlistEncoder两个类型来支持相应数据类型的编解码

  • 注意,以JSONDecoder为例,这里说的Coder与Encoder/Decoder协议没有任何关系
  • Encoder/Decoder的对象是和container绑定,也就是和编解码的中间数据绑定的
  • 这里说的Coder如JSONDecoder则不与任何数据绑定,是独立可复用的编解码器

特殊的case

  • json解析时,经常有这种情况,json中用一个字符串表示一个URL,但json中没有URL类型,所以如果遇到空字符串(可能json此时想表示无url值),但系统内部会认为这不是一个合法的URL格式数据,会解析失败

下面的内容时对源码的一点总结,涉及一个知识点--type eraser,但还未写完

JSONDecoder的decode源码分析

  1. JSONDecoder并不遵循Decoder协议
  2. JSONDecoder内部通过私有类_JSONDecoder进行实际的解码工作, _JSONDecoder是遵循Decoder协议的
  3. 解码第一步先用NSJSONSerialization将data转为jsonObject
  4. 解码时传入初始化方法init(from decoder: Decoder)的参数正是_JSONDecoder
  5. 通过func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey方法返回的是KeyedDecodingContainer类型
  6. KeyedDecodingContainer将各种具体的container(如json)包装到其中

KeyedDecodingContainer结构体

  • 定义为,struct KeyedDecodingContainer : KeyedDecodingContainerProtocol where K : CodingKey

  • typealias KeyedDecodingContainer.Key = K

  • 是从json到object之间的一个存储数据的中间结构

KeyedDecodingContainerProtocol

  • associatedtype Key

参考

  • Encoding and Decoding Custom Types
  • Understanding and Extending Swift 4’s Codable
  • Swift 4 JSON 解析进阶

你可能感兴趣的:(Codable in Swift)