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<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey

  • func singleValueContainer() throws -> SingleValueDecodingContainer

  • func unkeyedContainer() throws -> UnkeyedDecodingContainer

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

  • 形如Dictionary的数据,比如常见的最外层是jsonObject的json数据--对应container<Key>方法
  • 任意一个支持Decodable的类型,对应singleValueContainer方法
    • 比如下面的json数据是一个表示文本信息的JSONObject,要转成TextInfo的model,其中的color这个key对应一个字符串类型,表示一个颜色值
      {
          "color": "(12, 12, 12)",
          "text" : "I am songgeb!"
      }
      
    • 我们想把这个颜色值解析成一个自定义的颜色类型比如叫做MyColor
    • 一步步来,我们先在TextInfoinit方法中,通过container<Key>(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<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey方法返回的是KeyedDecodingContainer类型
  6. KeyedDecodingContainer将各种具体的container(如json)包装到其中

KeyedDecodingContainer结构体

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

  • typealias KeyedDecodingContainer<K>.Key = K

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

KeyedDecodingContainerProtocol

  • associatedtype Key

参考

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