Codable
协议在Swift4.0
开始被引入,目的是取代NSCoding
协议。Codable
协议对Swift
基本内嵌类型完美支持,能够把JSON弱类型数据转为代码中使用的强类型数据。
Codable
协议是Encodable
和Decodable
协议的组合,如果实现了Codable
,就表明实现了Encodable
和Decodable
。如果想要实现自定义类型或数据模型的编码和解码,必须遵循Codable
协议。
Swift
基本内嵌类型都默认遵循Codable
协议,比如String
、Int
、Double
、Date
和Data
。另外Array
、Dictionary
和Optional
也都遵循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
,也就是x
、y
。然后单方面赋值给x
和y
属性
继承
父类和子类无法同时遵循
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
定义:包含Encodable
和Decodable
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
KeyDecodingStrategy
:Key
的编码策略
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
协议- 使用
JSONSerialization
将data
数据序列化为字典的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
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
方法存储要解码的数据
_JSONDecodingStorage
是一个结构体,内部有Any
类型数组可存放任意类型,提供push
、popContainer
等方法,相当于一个容器
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
查看源码中
_JSONDecoder
的container
方法,返回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}
输出对应类型的文本,例如Bool
、Int32
当自己实现
init(from decoder: Decoder)
方法时,其中decode
和CodingKeys
都由系统自动生成
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}
上述代码,仅能编码出
age
和name
属性,但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
,系统自动生成了CodingKeys
和encode(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
协议,只允许使用struct
、enum
、class
编译报错但
LGPerson
是个协议,无法实现init
和encode
方法
这时可以考虑使用中间层来解决问题
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
:遇到继承和多态模式,需要手动实现编解码功能以上类库相比较而言,
HandyJSON
和Codable
更具优势