Codable简介
Codable
是在Swift4.0
开始被引入的新特性,目的是取代NSCoding
协议。
-
Codable
能够将程序内部的数据结构序列化成可交换数据,也能够将通用数据格式反序列化为内部使用的数据结构,大大提升对象和其表示之间互相转换的体验。 -
Codable
协议对Swift
基本内嵌类型完美支持,它对结构体,枚举和类都支持,能够把JSON这种弱类型数据转换成代码中使用的强类型数据,同时由于编译器的帮助,可以使开发者少写很多重复代码。 -
Swift
基本内嵌类型都默认遵循Codable
协议,比如String
、Int
、Double
、Date
和Data
。另外Array
、Dictionary
和Optional
也都遵循Codable
协议,可以直接使用编码和解码。 -
Codable
是一种混合类型,是Encodable
和Decodable
协议的组合,如果实现了Codable
,就表明实现了Encodable
和Decodable
。如果想要实现自定义类型或数据模型的编码和解码,必须遵循Codable
协议。
/// A type that can convert itself into and out of an external representation.
/// `Codable` is a type alias for the `Encodable` and `Decodable` protocols.
/// When you use `Codable` as a type or a generic constraint, it matches
/// any type that conforms to both protocols.
public typealias Codable = Decodable & Encodable
/// 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
}
/// 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
}
基本用法
看上面代码:
Decodable
Decodable协议定义了一个初始化函数:
init(from decoder: Decoder) throws
遵从Decodable协议的类型可以使用任何Decoder对象进行初始化,完成一个解码过程。 例如:
struct CPerson: Decodable{
var name: String
var age: Int
}
let jsonString = """
{
"age": 18,
"name": "吴彦祖",
}
"""
let jsonData = jsonString.data(using: .utf8)
if let data = jsonData{
let jsonDecoder = JSONDecoder()
let t = try? jsonDecoder.decode(CPerson.self, from: data)
print(t ?? "解析失败")
}
JSONDecoder是一个解析器,他可以解析实现Decodable协议的任何类型。
Encodable
Encodable协议定义了一个方法:
func encode(to encoder: Encoder) throws
任何Encoder对象,只要创建的时候遵从了Encodable协议类型,就表示完成一个编码过程。 由于Swift标准库中的类型,比如String,Int,Double和 Foundation 框架中Data,Date,URL都是默认支持Codable协议的,所以只需声明支持协议即可。
let t = CPerson(name: "刘", 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)
}
JSONEncoder是一个编码器,他可以解析实现Encodable协议的任何类型。
嵌套的模型
对于嵌套模型,必须保证所有模型都遵循Codable
协议,才能进行编码和解码。
如果出现未遵循Codable
协议的类型,编译报错。
JSON数据中有Optional values
日常开发中,和服务端对接数据难免出现null
,如果处理不当很可能程序crash
兼容方案:将可能为null
的属性使用声明为Optional
可选值
包含数组
保证数组的Item都遵循Codable
协议,才能进行编码和解码。
数组集合
当解析类型为数组集合,在decode
方法传入[<Type>].self
即可
例如:
let t = try? jsonDecoder.decode([CPerson].self, from: data)
继承
父类和子类无法同时遵循Codable
协议,否则编译报错
如果父类继承了Codable,则子类的成员无法解析和编码。原因很简单,就是由于子类没实现协议函数。
class CPerson: Codable {
var name: String?
}
class SuperMan: CPerson {
var color: String?
}
let jsonString = """
{
"name": "Zang",
"color": "Red"
}
"""
let jsonData = jsonString.data(using: .utf8)
if let data = jsonData{
let jsonDecoder = JSONDecoder()
let c = try? jsonDecoder.decode(SuperMan.self, from: data)
print("name:\(c?.name),partTime:\(c?.color)")
}
///result
name:Optional("Zang"),color:nil
上述代码,父类遵循Codable
协议,仅父类的属性可以被解析,子类的属性被解析为nil
同理:子类遵循Codable
协议,仅子类的属性可以被解析,父类的属性被解析为nil
怎么解决,我们后面再说。
元组类型
⽐如⼀个point,location : [20, 10]
,当使⽤Codable
进⾏解析的过程中,需要实现init(from decoder: Decoder)
方法
struct Point: Codable {
var x: Double
var y: Int
init(from decoder: Decoder) throws{
var contaioner = try decoder.unkeyedContainer()
self.x = try contaioner.decode(Double.self)
self.y = try contaioner.decode(Int.self)
}
}
struct RawSeverResponse: Codable{
var location: Point
}
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 ?? "解析失败")
}
如上代码,使用unkeyedContainer
,表示不解析当前的Key
,也就是x
、y
。然后单方面赋值给x
和y
属性,解析元组,不能使用直接和swift元组对应,只能通过结构体
协议
当结构体遵循自定义协议,同时也遵循Codable
协议,就可以成功编解码。
或者让自定义协议遵循Codable
协议,也可以成功编解码。
protocol CProtocol {
var name: String{ get set }
}
protocol BProtocol: Codable {
var name: String{ get set }
}
struct CPersion: CProtocol, Codable {
var name: String
var partTime: Int?
}
struct BPersion: BProtocol {
var name: String
var partTime: Int?
}
let jsonString = """
{
"name": "林志林",
"partTime": 20
}
"""
let jsonData = jsonString.data(using: .utf8)
if let data = jsonData{
let jsonDecoder = JSONDecoder()
let t = try jsonDecoder.decode(CPersion.self, from: data)
let t1 = try jsonDecoder.decode(BPersion.self, from: data)
print(t)
print(t1)
}
//result
CPersion(name: "林志林", partTime: Optional(20))
BPersion(name: "林志林", partTime: Optional(20))
数据和对象存在结构差异
遇到服务端返回数据和客户端对象存在结构差异的情况,可以这样处理:
struct CPerson: 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": "嗷",
"item.0": "压",
"item.2": "靠",
"item.1": "鹰"
}
"""
let jsonData = jsonString.data(using: .utf8)
if let data = jsonData {
let jsonDecoder = JSONDecoder()
let t = try jsonDecoder.decode(CPerson.self, from: data)
print(t)
}
//CPerson(elements: ["压", "鹰", "靠", "嗷"])
上述代码,实现init(from decoder: Decoder)
方法自定义解析,让CodingKeys
遵循CaseIterable
协议,使枚举类型具有可遍历的特性。仅需要解码功能,可以只遵循Decodable
协议
源码解析
上面我们介绍了Codable的基本用法,下面我们看看Codable的源码实现:
上面说到了,Codable
定义:包含Encodable
和Decodable
public typealias Codable = Decodable & Encodable
Decodable
Decodable
:解码,用于弱类型数据向自定义类型的转换
一个自定义的类型,只要实现init(from decoder: Decoder) throws
,就可以解析。
public protocol Decodable {
init(from decoder: Decoder) throws
}
Decoder
Decodable
的 init
方法内的Decoder
也是一个协议,它提供了如何解码数据类型的协议,定义如下:
public protocol Decoder {
var codingPath: [CodingKey] { get }
var userInfo: [CodingUserInfoKey : Any] { get }
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
func unkeyedContainer() throws -> UnkeyedDecodingContainer
func singleValueContainer() throws -> SingleValueDecodingContainer
}
JSONDecoder
Decoder
提供了一个解码器JSONDecoder
,定义如下:
open class JSONDecoder {
public enum DateDecodingStrategy {
case deferredToDate
case secondsSince1970
case millisecondsSince1970
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
case formatted(DateFormatter)
case custom((_ decoder: Decoder) throws -> Date)
}
public enum DataDecodingStrategy {
case deferredToData
case base64
case custom((_ decoder: Decoder) throws -> Data)
}
public enum NonConformingFloatDecodingStrategy {
case `throw`
case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
}
public enum KeyDecodingStrategy {
case useDefaultKeys
case convertFromSnakeCase
case custom((_ codingPath: [CodingKey]) -> CodingKey)
fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else {
return stringKey
}
var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
stringKey.formIndex(before: &lastNonUnderscore)
}
let keyRange = firstNonUnderscore...lastNonUnderscore
let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
let components = stringKey[keyRange].split(separator: "_")
let joinedString : String
if components.count == 1 {
joinedString = String(stringKey[keyRange])
} else {
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
}
let result : String
if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
result = joinedString
} else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
} else if (!leadingUnderscoreRange.isEmpty) {
result = String(stringKey[leadingUnderscoreRange]) + joinedString
} else {
result = joinedString + String(stringKey[trailingUnderscoreRange])
}
return result
}
}
open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
open var dataDecodingStrategy: DataDecodingStrategy = .base64
open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw
open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
open var userInfo: [CodingUserInfoKey : Any] = [:]
fileprivate struct _Options {
let dateDecodingStrategy: DateDecodingStrategy
let dataDecodingStrategy: DataDecodingStrategy
let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
let keyDecodingStrategy: KeyDecodingStrategy
let userInfo: [CodingUserInfoKey : Any]
}
fileprivate var options: _Options {
return _Options(dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
keyDecodingStrategy: keyDecodingStrategy,
userInfo: userInfo)
}
public init() {}
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
}
这里大体看就是JSONDecoder是Decoder的具体实现,它包含各种设置和操作。 我们一个个来看
DateDecodingStrategy
JSONDecoder
类定义了DateDecodingStrategy
枚举类型,返回何种策略的日期格式,案例如下:
let jsonDecoder = JSONDecoder()
//deferredToDate:默认策略
jsonDecoder.dateDecodingStrategy = .deferredToDate
print("deferredToDate=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
//secondsSince1970:距离1970.01.01的秒数
jsonDecoder.dateDecodingStrategy = .secondsSince1970
print("secondsSince1970=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
//距离1970.01.01的毫秒数
jsonDecoder.dateDecodingStrategy = .millisecondsSince1970
print("millisecondsSince1970=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
//解码为ISO-8601格式(RFC 3339格式)
jsonDecoder.dateDecodingStrategy = .iso8601
print("iso8601=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
//后台自定义的格式,使用DateFormatter解析
let formatter = DateFormatter()
formatter.dateFormat = "yyyy年MM月dd日 时间HH:mm:ss"
jsonDecoder.dateDecodingStrategy = .formatted(formatter)
print("formatted=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
//自定义格式,通过闭包表达式返回Date类型
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
}
print("custom=====>\(try jsonDecoder.decode(CPerson.self, from: data))")
上面根据服务器的数据类型,来设置相应的时间格式
DataDecodingStrategy
DataDecodingStrategy
:二进制解码策略
deferredToData
:默认解码策略base64
:使用base64
解码custom
:自定义方式解码
NonConformingFloatDecodingStrategy
NonConformingFloatDecodingStrategy
:不合法浮点数的编码策略
throw
convertFromString
KeyDecodingStrategy
KeyDecodingStrategy
:Key
的编码策略
useDefaultKeys
convertFromSnakeCase
custom
decode
方法
decode
方法用于将JSON
转为指定类型,接收T.Type
类型和Data
数据
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
- 入参的泛型
T
必须遵循Decodable
协议 - 使用
JSONSerialization
将data
数据序列化为字典的KeyValue
- 调用内部类
_JSONDecoder
传入字典和编码策略返回decoder
对象 - 通过
decoder
对象的unbox
方法解码并返回value
我们看看内部类_JSONDecoder
这里很重要
_JSONDecoder
_JSONDecoder
是用来解码操作的内部类,它遵循了Decoder
协议
具体代码:
fileprivate class _JSONDecoder : Decoder {
fileprivate var storage: _JSONDecodingStorage
fileprivate let options: JSONDecoder._Options
fileprivate(set) public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
self.storage = _JSONDecodingStorage()
self.storage.push(container: container)
self.codingPath = codingPath
self.options = options
}
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard !(self.storage.topContainer is NSNull) else {
throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
guard let topContainer = self.storage.topContainer as? [String : Any] else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer)
}
let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
return KeyedDecodingContainer(container)
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard !(self.storage.topContainer is NSNull) else {
throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get unkeyed decoding container -- found null value instead."))
}
guard let topContainer = self.storage.topContainer as? [Any] else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer)
}
return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return self
}
}
init
我们先看decode
方法里面调用的构造器方法:
init
方法,有三个参数传入
container
:序列化后的KeyValue
codingPath
:CodingKey
类型的空数组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
方法存储要解码的数据container - 初始化 options 和 codingPath(空数组)
_JSONDecodingStorage
_JSONDecodingStorage
是一个结构体,内部有Any
类型数组可存放任意类型,提供push
、popContainer
等方法,相当于一个栈容器,它是管理我们传入的container的,后面讲 container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key>
再细说
我们继续看unbox干了什么
unbox
unbox
方法用于解码操作,匹配对应的类型然后执行条件分支
fileprivate func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
return try unbox_(value, as: type) as? T
}
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
#if DEPLOYMENT_RUNTIME_SWIFT
if type == Date.self {
guard let date = try self.unbox(value, as: Date.self) else { return nil }
return date
} else if type == Data.self {
guard let data = try self.unbox(value, as: Data.self) else { return nil }
return data
} else if type == URL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url
} else if type == Decimal.self {
guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil }
return decimal
} else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
return try self.unbox(value, as: stringKeyedDictType)
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
#else
if type == Date.self || type == NSDate.self {
return try self.unbox(value, as: Date.self)
} else if type == Data.self || type == NSData.self {
return try self.unbox(value, as: Data.self)
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url
} else if type == Decimal.self || type == NSDecimalNumber.self {
return try self.unbox(value, as: Decimal.self)
} else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker.Type {
return try self.unbox(value, as: stringKeyedDictType)
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
#endif
}
这里面有不同的unbox分支,会根据T的不同,做相应的操作。
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 }
}
查看针对_JSONStringDictionaryDecodableMarker
类型的解码方法
fileprivate func unbox<T>(_ value: Any, as type: _JSONStringDictionaryDecodableMarker.Type) throws -> T? {
guard !(value is NSNull) else { return nil }
var result = [String : Any]()
guard let dict = value as? NSDictionary else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let elementType = type.elementType
for (key, value) in dict {
let key = key as! String
self.codingPath.append(_JSONKey(stringValue: key, intValue: nil))
defer { self.codingPath.removeLast() }
result[key] = try unbox_(value, as: elementType)
}
return result as? T
}
对于_JSONStringDictionaryDecodableMarker
类型的解码过程,其实就是一个递归操作,这个是对于dic的解码
其他的类似
我们这里主要研究分支的最后一段
else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
源码中type.init(from:)
方法,传入的self
,本质是_JSONDecoder
type就是我们要解析的value的类型,结合上面的流程,那么init(from:)
即应该是我们Decodable协议中的init方法
public protocol Decodable {
init(from decoder: Decoder) throws
}
那这里有个疑问。我们前面都是只继承了Decodable
,好像从来没有实现过
init(from decoder: Decoder) throws
它在哪里实现的呢?
使用命令
swiftc -emit-sil main.swift | xcrun swift-demangle
我们看只继承了Decodable的数据结构,它的SIL是什么样子的。
struct CPerson: Decodable{
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(CPerson.self, from: jsonData!)
print("-----end")
上面是一个简单的Decoder解码
import Foundation
struct CPerson : Decodable {
@_hasStorage var name: String { get set }
@_hasStorage var age: Int { get set }
enum CodingKeys : CodingKey {
case name
case age
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: CPerson.CodingKeys, _ b: CPerson.CodingKeys) -> Bool
func hash(into hasher: inout Hasher)
init?(stringValue: String)
init?(intValue: Int)
var hashValue: Int { get }
var intValue: Int? { get }
var stringValue: String { get }
}
init(from decoder: Decoder) throws
init(name: String, age: Int)
}
- 编译器自动实现
CodingKeys
枚举类型,并遵循CodingKey
协议。解码过程中会通过CodingKeys
找到对应case
- 编译器自动实现
decode
解码方法:init(from decoder: Decoder)
这也是 Codable的最方便之处,苹果帮我们实现了一套基本类型的解析方案。
我们再看CPerson.init(from:)
/ CPerson.init(from:)
sil hidden @main.CPerson.init(from: Swift.Decoder) throws -> main.CPerson : $@convention(method) (@in Decoder, @thin CPerson.Type) -> (@owned CPerson, @error Error) {
// %0 "decoder" // users: %69, %49, %9, %6
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin CPerson.Type):
%2 = alloc_stack $Builtin.Int2 // users: %70, %27, %5, %78, %52
%3 = alloc_stack [dynamic_lifetime] $CPerson, 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<CPerson.CodingKeys>, let, name "container" // users: %45, %44, %37, %66, %65, %21, %60, %59, %13, %55
%9 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder // users: %13, %13, %12
%10 = metatype $@thin CPerson.CodingKeys.Type
%11 = metatype $@thick CPerson.CodingKeys.Type // user: %13
%12 = witness_method $@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %9 : $*@opened("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") 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("F185EE86-7F23-11EC-ABA0-A0999B1CB1FF") Decoder, CPerson.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
方法并调用
是不是就是干下面的活儿呢?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try container.decode(Int.self, forKey: .age)
name = try container.decode(String.self, forKey: .name)
}
这个很熟悉吧。
KeyedDecodingContainer
所以这里我们再看看container,以及container.decode,decoder.container
再看看Decodable协议,Decoder
协议中存在container
方法的声明
查看源码中
_JSONDecoder
的container
方法,返回KeyedDecodingContainer<Key>
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard !(self.storage.topContainer is NSNull) else {
throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
guard let topContainer = self.storage.topContainer as? [String : Any] else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer)
}
let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: topContainer)
return KeyedDecodingContainer(container)
}
KeyedDecodingContainer<K>
是一个结构体,遵循KeyedDecodingContainerProtocol
协议。有一个条件限制,K
必须遵循CodingKey
协议。结构体内定义各种类型的解码方法,会根据不同类型匹配到对应的decode
方法
public struct KeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K : CodingKey {
public typealias Key = K
public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedDecodingContainerProtocol
public var codingPath: [CodingKey] { get }
public var allKeys: [KeyedDecodingContainer<K>.Key] { get }
public func contains(_ key: KeyedDecodingContainer<K>.Key) -> Bool
public func decodeNil(forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
public func decode(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool
public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String
public func decode(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double
public func decode(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float
public func decode(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int
public func decode(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8
public func decode(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16
public func decode(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32
public func decode(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64
public func decode(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt
public func decode(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8
public func decode(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16
public func decode(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32
public func decode(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64
public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T where T : Decodable
public func decodeIfPresent(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool?
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?
public func decodeIfPresent(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double?
public func decodeIfPresent(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float?
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?
public func decodeIfPresent(_ type: Int8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int8?
public func decodeIfPresent(_ type: Int16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int16?
public func decodeIfPresent(_ type: Int32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int32?
public func decodeIfPresent(_ type: Int64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int64?
public func decodeIfPresent(_ type: UInt.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt?
public func decodeIfPresent(_ type: UInt8.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt8?
public func decodeIfPresent(_ type: UInt16.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt16?
public func decodeIfPresent(_ type: UInt32.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt32?
public func decodeIfPresent(_ type: UInt64.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> UInt64?
public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T : Decodable
public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey
public func nestedUnkeyedContainer(forKey key: KeyedDecodingContainer<K>.Key) throws -> UnkeyedDecodingContainer
public func superDecoder() throws -> Decoder
public func superDecoder(forKey key: KeyedDecodingContainer<K>.Key) throws -> Decoder
public func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool?
public func decodeIfPresent(_ type: String.Type, forKey key: K) throws -> String?
public func decodeIfPresent(_ type: Double.Type, forKey key: K) throws -> Double?
public func decodeIfPresent(_ type: Float.Type, forKey key: K) throws -> Float?
public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int?
public func decodeIfPresent(_ type: Int8.Type, forKey key: K) throws -> Int8?
public func decodeIfPresent(_ type: Int16.Type, forKey key: K) throws -> Int16?
public func decodeIfPresent(_ type: Int32.Type, forKey key: K) throws -> Int32?
public func decodeIfPresent(_ type: Int64.Type, forKey key: K) throws -> Int64?
public func decodeIfPresent(_ type: UInt.Type, forKey key: K) throws -> UInt?
public func decodeIfPresent(_ type: UInt8.Type, forKey key: K) throws -> UInt8?
public func decodeIfPresent(_ type: UInt16.Type, forKey key: K) throws -> UInt16?
public func decodeIfPresent(_ type: UInt32.Type, forKey key: K) throws -> UInt32?
public func decodeIfPresent(_ type: UInt64.Type, forKey key: K) throws -> UInt64?
public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable
}
上述代码,结构体中定义了很多类型的decode
方法,这些方法由苹果内部工具生成,利用Codable.swift.gyb
模板文件生成Codable.swift
源文件。
Codable.swift.gyb模板文件:
定义集合存放所有可编解码的内嵌类型。以%
开始和结束,视为代码的开始和结束,通过python
控制,相当于模板文件
没有以
%
开始和结束,视为文本直接输出。
这里就不细研究了。
当自己实现init(from decoder: Decoder)
方法时,其中decode
和CodingKeys
都由系统自动生成,所以我们就可以自己重新定义CodingKeys
来解决数据和对象存在结构差异的情况。
也可以重新实现init(from decoder: Decoder)
方法。
Decodable总结
这里有张图,作为总结我觉得都挺清晰的
Encodable
Encodable
:编码,用于自定义类型向弱类型数据的转换
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
}
Encoder
Encodable 提供的方法 func encode(to encoder: Encoder) throws
里面的Encoder也是一个协议,
public protocol Encoder {
/// The path of coding keys taken to get to this point in encoding.
var codingPath: [CodingKey] { get }
/// Any contextual information set by the user for encoding.
var userInfo: [CodingUserInfoKey : Any] { get }
/// Returns an encoding container appropriate for holding multiple values
/// keyed by the given key type.
///
/// You must use only one kind of top-level encoding container. This method
/// must not be called after a call to `unkeyedContainer()` or after
/// encoding a value through a call to `singleValueContainer()`
///
/// - parameter type: The key type to use for the container.
/// - returns: A new keyed encoding container.
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey
/// Returns an encoding container appropriate for holding multiple unkeyed
/// values.
///
/// You must use only one kind of top-level encoding container. This method
/// must not be called after a call to `container(keyedBy:)` or after
/// encoding a value through a call to `singleValueContainer()`
///
/// - returns: A new empty unkeyed container.
func unkeyedContainer() -> UnkeyedEncodingContainer
/// Returns an encoding container appropriate for holding a single primitive
/// value.
///
/// You must use only one kind of top-level encoding container. This method
/// must not be called after a call to `unkeyedContainer()` or
/// `container(keyedBy:)`, or after encoding a value through a call to
/// `singleValueContainer()`
///
/// - returns: A new empty single value container.
func singleValueContainer() -> SingleValueEncodingContainer
}
看这个🌰
struct CPerson: Encodable {
var name: String
var age: Int
}
let value = CPerson(name: "liu", age: 99)
let jsonEncoder = JSONEncoder()
let data = try? jsonEncoder.encode(value)
let str = String(data: data!, encoding: .utf8)
print(str)
encode
方法,接收泛型T
,泛型必须遵循Encodable
协议,返回Data
数据
open func encode<T : Encodable>(_ value: T) throws -> Data {
let encoder = _JSONEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level (T.self) did not encode any values."))
}
let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue).union(.fragmentsAllowed)
do {
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
} catch {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to JSON.", underlyingError: error))
}
}
这个流程刚好与Decoder是相反的
- 创建内部类
_JSONEncoder
- 调用
box_
方法包装成字典类型 - 使用
JSONSerialization
序列化为Data
数据
我们先看_JSONEncoder
_JSONEncoder
_JSONEncoder
类遵循Encoder
协议,主要提供container
编码方法,返回KeyedEncodingContainer<Key>
fileprivate class _JSONEncoder : Encoder {
fileprivate var storage: _JSONEncodingStorage
fileprivate let options: JSONEncoder._Options
public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
fileprivate init(options: JSONEncoder._Options, codingPath: [CodingKey] = []) {
self.options = options
self.storage = _JSONEncodingStorage()
self.codingPath = codingPath
}
fileprivate var canEncodeNewValue: Bool {
return self.storage.count == self.codingPath.count
}
public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
let topContainer: NSMutableDictionary
if self.canEncodeNewValue {
topContainer = self.storage.pushKeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableDictionary else {
preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
let container = _JSONKeyedEncodingContainer<Key>(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
return KeyedEncodingContainer(container)
}
public func unkeyedContainer() -> UnkeyedEncodingContainer {
let topContainer: NSMutableArray
if self.canEncodeNewValue {
topContainer = self.storage.pushUnkeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableArray else {
preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
return _JSONUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
}
public func singleValueContainer() -> SingleValueEncodingContainer {
return self
}
fileprivate func box_(_ value: Encodable) throws -> NSObject? {
......
do {
//调用Encodable的方法
try value.encode(to: self)
}
......
}
}
box_
box_
方法,根据value
的不同类型,调用不同的代码分支,将value
包装成对应的数据类型。
下面代码,如果value
不是上述定义的数据类型,例如CPerson
,最终会调用value.encode(to: self)
方法,传入的self
就是_JSONEncoder
return try box(value as! [String : Encodable])
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()
}
container方法提供KeyedEncodingContainer
KeyedEncodingContainer
KeyedEncodingContainer<K>
结构体,遵循KeyedEncodingContainerProtocol
协议,要求K
必须遵循CodingKey
协议,内部定义了各种类型对应的encode
方法
public struct KeyedEncodingContainer<K> : KeyedEncodingContainerProtocol where K : CodingKey {
public typealias Key = K
public init<Container>(_ container: Container) where K == Container.Key, Container : KeyedEncodingContainerProtocol
public var codingPath: [CodingKey] { get }
public mutating func encodeNil(forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Bool, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: String, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Double, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Float, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Int, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Int8, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Int16, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Int32, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: Int64, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: UInt, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: UInt8, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: UInt16, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: UInt32, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode(_ value: UInt64, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encode<T>(_ value: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
public mutating func encodeConditional<T>(_ object: T, forKey key: KeyedEncodingContainer<K>.Key) throws where T : AnyObject, T : Encodable
public mutating func encodeIfPresent(_ value: Bool?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: String?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Double?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Float?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Int?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Int8?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Int16?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Int32?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: Int64?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: UInt?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: UInt8?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: UInt16?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: UInt32?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent(_ value: UInt64?, forKey key: KeyedEncodingContainer<K>.Key) throws
public mutating func encodeIfPresent<T>(_ value: T?, forKey key: KeyedEncodingContainer<K>.Key) throws where T : Encodable
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: KeyedEncodingContainer<K>.Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey
public mutating func nestedUnkeyedContainer(forKey key: KeyedEncodingContainer<K>.Key) -> UnkeyedEncodingContainer
public mutating func superEncoder() -> Encoder
public mutating func superEncoder(forKey key: KeyedEncodingContainer<K>.Key) -> Encoder
public mutating func encodeConditional<T>(_ object: T, forKey key: K) throws where T : AnyObject, T : Encodable
public mutating func encodeIfPresent(_ value: Bool?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: String?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Double?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Float?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Int?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Int8?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Int16?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Int32?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: Int64?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: UInt?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: UInt8?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: UInt16?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: UInt32?, forKey key: K) throws
public mutating func encodeIfPresent(_ value: UInt64?, forKey key: K) throws
public mutating func encodeIfPresent<T>(_ value: T?, forKey key: K) throws where T : Encodable
}
Encodable总结
这里分析的比较初略,因为基本思想和Decodable是一样的
踩坑记
Codable不能继承
这个最常见,前面使用中我们也提到过,父类继承Codable,子类是不能完全继承的,也就是说,子类的新元素不能够被Encode或者Decode,很简单,因为
- 父类遵循了
Codable
协议,所以系统针对父类自动生成了encode(to encoder: Encoder)
方法 - 子类虽然继承自父类,但并没有重写
encode(to encoder: Encoder)
方法 - 所以在编码过程中,找到的依然是父类的
encode
方法,最终仅父类属性可以被成功编码 通过SIL可以看得很清楚
class CPerson : 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: CPerson.CodingKeys, _ b: CPerson.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
}
针对CPerson
,系统自动生成了CodingKeys
和encode(to encoder: Encoder)
方法
看他的子类CSuperMan
@_inheritsConvenienceInitializers class CSuperMan : CPerson {
@_hasStorage @_hasInitialValue var subjectName: String? { get set }
@objc deinit
override init()
required init(from decoder: Decoder) throws
}
可以通过重写子类的encode(to encoder: Encoder)
方法解决。
class CSuperMan: CPerson {
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)
}
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)
}
}
注意try super.encode(to: encoder)
如果在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}}
多态模式下的编解码问题
当结构体存储自定义协议,即使协议遵循Codable
协议,依然编译报错,提示:协议类型不符合Decodable
协议,只允许使用struct
、enum
、class
这时可以考虑类型擦除使用中间层来解决问题
protocol BaseInfo {
var name: String { get set }
var age: Int { get set }
}
struct SuperPerson: BaseInfo {
var name: String
var age: Int
}
struct HeroPerson: BaseInfo {
var name: String
var age: Int
}
struct PersonBox: BaseInfo, Codable {
var name: String
var age: Int
init(_ baseinfo:BaseInfo) throws {
self.name = baseinfo.name
self.age = baseinfo.age
}
}
struct Group: Codable {
var groupName: String
var person:[PersonBox]
}
let person: [BaseInfo] = [SuperPerson(name: "蜘蛛侠", age: 99), HeroPerson(name: "悟空", age: 500)]
let array = try person.map(PersonBox.init)
let aGroup = Group(groupName: "Axiaozu", person: array)
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(aGroup)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print("编码:\(jsonString)")
}
print("\n--------------------\n")
let jsonDecoder = JSONDecoder()
let c = try? jsonDecoder.decode(Group.self, from: jsonData)
print("解码:\(c)")
///result
编码:{
"groupName" : "Axiaozu",
"person" : [
{
"name" : "蜘蛛侠",
"age" : 99
},
{
"name" : "悟空",
"age" : 500
}
]
}
--------------------
解码:Optional(LGMirror.Group(groupName: "Axiaozu", person: [LGMirror.PersonBox(name: "蜘蛛侠", age: 99), LGMirror.PersonBox(name: "悟空", age: 500)]))
上述代码,编码和解码都能执行成功,但解码后输出的类型都是LGPersonBox
。如果需要保留原始的类型信息,应该怎样处理?
方案一 使用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"))
和其他编解码类库的对比
SwiftyJSON
:使用下标的方式取值,很容易数组越界ObjectMapper
:手动对每一个对象提供映射关系,代码量很大HandyJSON
:使用内存赋值的方式进行编解码操作,原理和Codable
殊途同归Codable
:遇到继承和多态模式,需要手动实现编解码功能 以上类库相比较而言,HandyJSON
和Codable
更具优势