Swift 4.0
后引入的特性甸各,目標(biāo)是取代NSCoding
協(xié)議。對結(jié)構(gòu)體焰坪,枚舉和類都支持趣倾,能夠把JSON
這種弱類型數(shù)據(jù)轉(zhuǎn)換成代碼中使用的強類型數(shù)據(jù),同時由于編譯器的幫助某饰,可以少寫很多重復(fù)代碼儒恋。
Codable
協(xié)議被設(shè)計出來用于替代NSCoding
協(xié)議善绎,所以遵從Codable
協(xié)議的對象就可以無縫的支持NSKeyedArchive
r和NSKeyedUnarchiver
對象進(jìn)行Archive&UnArchive
持久化和反持久化。原有的解碼過程和持久化過程需要單獨處理诫尽,現(xiàn)在通過新的Codable
協(xié)議一起搞定禀酱。
Codable
定義:(由Decodable
和Encodable
協(xié)議組成):
public typealias Codable = Decodable & Encodable
Decodable
協(xié)議定義了一個初始化函數(shù):init
,遵從Decodable
協(xié)議的類型可以使用任何Decoder
對象進(jìn)行初始化,完成解碼過程。
public protocol Decodable {
init(from decoder: Decoder) throws
}
Encodable
協(xié)議定義了一個encode
方法, 任何Encoder
對象都可以創(chuàng)建遵從了Encodable
協(xié)議類型的表示箱锐,完成編碼過程比勉。
public protocol Encodable {
func encode(to encoder: Encoder) throws
}
Swift
標(biāo)準(zhǔn)庫中的類型String
、Int
驹止、Double
以及Foundation
框架中Data
浩聋、Date
、URL
都是默認(rèn)支持Codable
協(xié)議的臊恋,只需聲明支持協(xié)議即可褂乍。
基礎(chǔ)語法
一個最簡單的例子:
struct Hotpot: Codable{
var name: String
var age: Int
var height: Double
}
Hotpot
遵循了Codable
協(xié)議撒璧,他就默認(rèn)有了init(from:)
和encode(to:)
方法。
原始數(shù)據(jù)(為了演示方便以字符串代替):
let jsonString = """
{
"age": 10,
"name": "cat",
"height": 1.85
}
"""
解碼
let jsonData = jsonString.data(using: .utf8)
//創(chuàng)建解碼器
let jsonDecoder = JSONDecoder()
if let data = jsonData {
//解碼,參數(shù):數(shù)據(jù)類型,原始data數(shù)據(jù)
let hotpot = try? jsonDecoder.decode(Hotpot.self, from: data)
print(hotpot ?? "error")
}
//輸出
Hotpot(name: "cat", age: 10, height: 1.85)
- 創(chuàng)建解碼器
- 解碼
decode
傳入類型(Hotpot.self
)和數(shù)據(jù)(data
)
編碼
let jsonEncode = JSONEncoder()
let jsonData1 = try? jsonEncode.encode(hotpot)
if let data = jsonData1 {
let jsonString1 = String(decoding: data, as: UTF8.self)
print(jsonString1)
}
//輸出
{"name":"cat","age":10,"height":1.8500000000000001}
這里發(fā)現(xiàn)在encode
的時候精度出現(xiàn)了問題:
解決方案大致分為以下幾種:
- 浮點數(shù)轉(zhuǎn)
string
處理逮壁。在encode
時候double
值轉(zhuǎn)為String
就能避免這個問題。 - 使用
NSDecimalNumber
來接收艳悔,由于NSDecimalNumber
無法遵循Codable
協(xié)議(原因)集嵌,所以需要用Decimal
來接收數(shù)據(jù),NSDecimalNumber
作為計算屬性來處理數(shù)據(jù)放吩。 - 三方庫 金額計算智听。
對于第二點:
Synthesizing conformace to Codable, Equatable and Hashable in different source files is currently not supported by the Swift compiler。
At the moment (Xcode 10.2.1 / Swift 5.0.1)Codable
currently isn't supported yet if an extension in one file adds conformance in a different file. Check this out at https://bugs.swift.org/. https://bugs.swift.org/browse/SR-6101
大概意思是目前Swift
不支持在不同的源文件渡紫,合成conformace
到Codable
到推、Equatable
和Hashable
。
什么意思呢惕澎?看個例子就明白了:
在A
文件中定義一個class
莉测,在B
文件擴展這個class
遵循協(xié)議codable
就會報錯了。//A文件: class HPTestCodable { } //B文件: extension HPTestCodable:Codable { }
image.png
解決精度問題
以第二種方式為例唧喉,這里僅僅是為了解決Encode
過程中height
精度問題:
struct Hotpot: Codable{
var name: String
var age: Int
var height: Decimal
var heightNumber: NSDecimalNumber {
get {
return NSDecimalNumber(decimal: height)
}
set {
height = newValue.decimalValue
}
}
}
調(diào)用:
/**json數(shù)據(jù)*/
let jsonString = """
{
"age": 10,
"name": "cat",
"height": 1.85
}
"""
let jsonData = jsonString.data(using: .utf8)
/**解碼*/
var hotpot:Hotpot?
if let data = jsonData {
//解碼,參數(shù):數(shù)據(jù)類型捣卤,原始data數(shù)據(jù)
hotpot = try? JSONDecoder().decode(Hotpot.self, from: data)
if let height = hotpot?.height.description {
print(height)
}
print(hotpot ?? "error")
}
/**編碼*/
let jsonEncode = JSONEncoder()
let jsonData1 = try? jsonEncode.encode(hotpot)
if let data = jsonData1 {
let jsonString = String(decoding: data, as: UTF8.self)
print(jsonString)
}
結(jié)果:
1.85
Hotpot(name: "cat", age: 10, height: 1.85)
{"name":"cat","age":10,"height":1.85}
這里看到decode、encode欣喧、取值
精度都沒有問題了腌零。
歸檔反歸檔
由于JSONEncoder
將數(shù)據(jù)轉(zhuǎn)換成了data
,我們可以直接對數(shù)據(jù)進(jìn)行存儲了唆阿,可以寫入本地文件益涧、數(shù)據(jù)庫等。以下簡單寫入UserDefaults
演示(當(dāng)然用NSKeyedArchiver
也可以):
if let data = try? JSONEncoder().encode(hotpot) {
let jsonString = String(decoding: data, as: UTF8.self)
//寫入UserDefaults
UserDefaults.standard.set(data, forKey: "hotpotData")
print(jsonString)
}
// 從UserDefaults讀取
if let data = UserDefaults.standard.data(forKey: "hotpotData") {
let jsonString = String(decoding: data, as: UTF8.self)
print(jsonString)
}
{"name":"cat","age":10,"height":1.85}
{"name":"cat","age":10,"height":1.85}
嵌套的模型
struct Hotpot: Codable{
var name: String
var age: Int
var height: Double
var cat: Cat
}
extension Hotpot {
struct Cat: Codable {
var name: String
var age: Int
}
}
let jsonString = """
{
"name": "hotpot",
"height": 1.85,
"age": 18,
"cat": {
"age": 1,
"name": "cat"
}
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData {
let result = try? decoder.decode(Hotpot.self, from: data)
print(result ?? "解析失敗")
}
輸出:
Hotpot(name: "hotpot", age: 18, height: 1.85, cat: SwiftProtocol.Hotpot.Cat(name: "cat", age: 1))
- 對于遵循了
Codable
協(xié)議的類型驯鳖,解碼器能夠自動識別模型的嵌套闲询。
包含數(shù)組
struct Hotpot: Codable{
var name: String
var age: Int
var height: Double
var cat: [Cat]
}
extension Hotpot {
struct Cat: Codable {
var name: String
var age: Int
}
}
let jsonString = """
{
"name": "hotpot",
"height": 1.85,
"age": 18,
"cat": [{
"age": 1,
"name": "cat"
},{
"age": 10,
"name": "cat1"
}]
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode(Hotpot.self, from: data)
print(result ?? "解析失敗")
}
Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])
可以看到不需要做額外的操作久免。
JSON數(shù)據(jù)是數(shù)組集合
let jsonString = """
[
{
"age" : 18,
"cat" : [
{
"age" : 1,
"name" : "cat"
},
{
"age" : 10,
"name" : "cat1"
}
],
"name" : "hotpot",
"height" : 1.85
},
{
"age" : 18,
"cat" : [
{
"age" : 1,
"name" : "cat"
},
{
"age" : 10,
"name" : "cat1"
}
],
"name" : "hotpot",
"height" : 1.85
}
]"""
//調(diào)用
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode([Hotpot].self, from: data)
print(result ?? "解析失敗")
}
[SwiftProtocol.Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)]), SwiftProtocol.Hotpot(name: "hotpot", age: 18, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])]
只需要解析模型對應(yīng)的類型傳數(shù)組[Hotpot].self
就ok了。
JSON數(shù)據(jù)中有 Optional values
一般情況下后臺可能會傳給我們null
值扭弧,如果上面的例子不做修改直接解析會失敗阎姥。處理不當(dāng)可能會crash。
一般情況下我們把可能為空的值聲明為可選值解決鸽捻。
struct Hotpot: Codable{
var name: String
var age: Int?
var height: Double
var cat: [Cat]
}
extension Hotpot {
struct Cat: Codable {
var name: String
var age: Int
}
}
let jsonString = """
[
{
"age" : 18,
"cat" : [
{
"age" : 1,
"name" : "cat"
},
{
"age" : 10,
"name" : "cat1"
}
],
"name" : "hotpot",
"height" : 1.85
},
{
"age" : null,
"cat" : [
{
"age" : 1,
"name" : "cat"
},
{
"age" : 10,
"name" : "cat1"
}
],
"name" : "hotpot",
"height" : 1.85
}
]
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let data = jsonData{
let result = try? decoder.decode([Hotpot].self, from: data)
print(result ?? "解析失敗")
}
[SwiftProtocol.Hotpot(name: "hotpot", age: Optional(18), height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)]), SwiftProtocol.Hotpot(name: "hotpot", age: nil, height: 1.85, cat: [SwiftProtocol.Hotpot.Cat(name: "cat", age: 1), SwiftProtocol.Hotpot.Cat(name: "cat1", age: 10)])]
元組類型
比如我們有一個坐標(biāo)呼巴,后臺服務(wù)器傳給"location": [114.121344, 38.908766]
,這個時候用數(shù)組接顯然不好維護(hù)御蒲。返回的數(shù)組怎么和模型對應(yīng)上呢? 這個時候就需要自己寫解析實現(xiàn)"init(from decoder: Decoder)"
struct Location: Codable {
var x: Double
var y: Double
init(from decoder: Decoder) throws {
//不解析當(dāng)前的key,也就是解碼時不要key值衣赶。
var contaioner = try decoder.unkeyedContainer()
//單方面把值給到x與y
self.x = try contaioner.decode(Double.self)
self.y = try contaioner.decode(Double.self)
}
}
struct RawSeverResponse: Codable {
var location: Location
}
let jsonString = """
{
"location": [114.121344, 38.908766]
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(RawSeverResponse.self, from: jsonData!)
print(result)
RawSeverResponse(location: SwiftProtocol.Location(x: 114.121344, y: 38.908766))
繼承
class Hotpot: Codable {
var name: String?
}
class HotpotCat: Hotpot {
var age: Int?
}
let jsonString = """
{
"name": "hotpot",
"age": 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)
按照猜想上面的代碼應(yīng)該age
和name
都能正常解析,看下輸出:
Optional("hotpot")
nil
age
沒有解析厚满?那么Codable
放在子類上呢府瞄?
class Hotpot {
var name: String?
}
class HotpotCat: Hotpot, Codable {
var age: Int?
}
let jsonString = """
{
"name": "hotpot",
"age": 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)
nil
Optional(18)
name
沒有解析。
這個時候就需要我們在派生類中重寫init(from decoder: Decoder)
碘箍,如果要編碼還需要encode(to encoder: Encoder)
并且需要定義CodingKeys
遵馆。
class Hotpot: Codable {
var name: String?
}
class HotpotCat: Hotpot {
var age: Int?
private enum CodingKeys: String,CodingKey{
case name
case age
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
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)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
try super.encode(to: encoder)
}
}
let jsonString = """
{
"name": "hotpot",
"age": 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(HotpotCat.self, from: jsonData!)
print(result.name)
print(result.age)
Optional("hotpot")
Optional(18)
為什么派生類不能自動解析?原理后面再分析丰榴。
協(xié)議
如果有協(xié)議的參與下解析會怎么樣呢货邓?
protocol HotpotProtocol: Codable {
var name: String{ get set }
}
struct Hotpot: HotpotProtocol {
var name: String
var age: Int?
}
let jsonString = """
{
"name": "hotpot",
"age": 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)
Hotpot(name: "hotpot", age: Optional(18))
可以看到能夠正常解析。
key值不同
對于一些不符合規(guī)范的字段四濒,我們一般是會給別名來解析逻恐,那么在Codable
中怎么實現(xiàn)呢?
比如后臺返回了這樣的數(shù)據(jù)峻黍,正常情況下應(yīng)該返回一個數(shù)組和對應(yīng)的type。
let jsonString = """
{
"01Type" : "hotpot",
"item.1" : "mouse",
"item.2" : "dog",
"item.3" : "hotpot",
"item.0" : "cat"
}
"""
//如果不需要編碼拨匆,直接遵循 Decodable 就好了
struct Hotpot: Decodable {
var type: String?
let elements: [String]
enum CodingKeys: String, CaseIterable, CodingKey {
case type = "01Type"
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)
self.type = try container.decode(String.self, forKey: .type)
var element: [String] = []
//方式一:遍歷 container
for item in container.allKeys {
switch item {
case .item0,.item1,.item2,.item3:
element.append(try container.decode(String.self, forKey: item))
default:
continue
}
}
//方式二:遍歷CodingKeys 和 contains比較裝入集合
// for item in CodingKeys.allCases {
// guard container.contains(item) else {
// continue
// }
// if item != .type {
// element.append(try container.decode(String.self, forKey: item))
// }
// }
self.elements = element
}
}
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)
Hotpot(type: Optional("hotpot"), elements: ["cat", "mouse", "dog", "hotpot"])
源碼解析
Decodable
public protocol Decodable {
init(from decoder: Decoder) throws
}
對應(yīng)的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
}
Encodable
public protocol Encodable {
func encode(to encoder: Encoder) throws
}
對應(yīng)的Encoder
提供具體編碼
public protocol Encoder {
var codingPath: [CodingKey] { get }
var userInfo: [CodingUserInfoKey : Any] { get }
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey
func unkeyedContainer() -> UnkeyedEncodingContainer
func singleValueContainer() -> SingleValueEncodingContainer
}
JSONDecoder
open class JSONDecoder {
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// 距離 1970.01.01的秒數(shù)
case secondsSince1970
/// 距離 1970.01.01的毫秒數(shù)
case millisecondsSince1970
///日期編碼格式
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
///后臺自定義格式姆涩,這個時候可以創(chuàng)建自定義DataFormatter來解析
case formatted(DateFormatter)
///自定義格式,提供一個閉包表達(dá)式
case custom((Decoder) throws -> Date)
}
/// 二進(jìn)制數(shù)據(jù)解碼策略
public enum DataDecodingStrategy {
/// 默認(rèn)
case deferredToData
/// base64
case base64
/// 自定義
case custom((Decoder) throws -> Data)
}
/// 不合法浮點數(shù)編碼策略
public enum NonConformingFloatDecodingStrategy {
/// Throw upon encountering non-conforming values. This is the default strategy.
case `throw`
/// Decode the values from the given representation strings.
case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
}
/// key值編碼策略
public enum KeyDecodingStrategy {
/// Use the keys specified by each type. This is the default strategy.
case useDefaultKeys
//指定去掉中間下劃線惭每,變成駝峰命名
case convertFromSnakeCase
//自定義
case custom(([CodingKey]) -> CodingKey)
}
open var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy
/// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
open var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy
/// Contextual user-provided information for use during decoding.
open var userInfo: [CodingUserInfoKey : Any]
/// Initializes `self` with default strategies.
///默認(rèn)init方法
public init()
/// Decodes a top-level value of the given type from the given JSON representation.
/// - parameter type: The type of the value to decode.
/// - parameter data: The data to decode from.
/// - returns: A value of the requested type.
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - throws: An error if any value throws an error during decoding.
open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}
DateDecodingStrategy
以何種策略解析日期格式骨饿。
let jsonString = """
{
"name" : "hotpot",
"age" : 18,
"date" : 1610390703
}
"""
struct Hotpot: Codable {
var name: String
var age: Double
var date: Date
}
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)
deferredToDate
"date" : 1610390703
decoder.dateDecodingStrategy = .deferredToDate
Hotpot(name: "hotpot", age: 18.0, date: 2052-01-12 18:45:03 +0000)
使用默認(rèn)格式無法正確推導(dǎo)出時間。
secondsSince1970
decoder.dateDecodingStrategy = .secondsSince1970
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)
millisecondsSince1970
"date" : 1610390703000
decoder.dateDecodingStrategy = .millisecondsSince1970
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)
iso8601
"date" : "2021-01-11T19:20:20Z"
decoder.dateDecodingStrategy = .iso8601
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 19:20:20 +0000)
formatted
"date" : "2021/01/11 19:20:20"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 11:20:20 +0000)
custom
custom相對比較靈活台腥,可以做一些錯誤處理以及格式不固定的情況下使用宏赘。
"date" : "1610390703"
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
return Date(timeIntervalSince1970: Double(dateStr)!)
})
Hotpot(name: "hotpot", age: 18.0, date: 2021-01-11 18:45:03 +0000)
func decode
源碼:
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {//泛型函數(shù),同時當(dāng)前泛型約束為遵循了Decodable的協(xié)議
let topLevel: Any
do {
//JSONSerialization將二進(jìn)序列化成json
topLevel = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
// __JSONDecoder 傳入序列化的json 和 self.options 編碼策略創(chuàng)建decoder對象
let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
//unbox 拆 topLevel 數(shù)據(jù) 返回解碼后的value
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
}
1.這里是一個泛型函數(shù)黎侈,傳入的參數(shù)T
要求遵守 Decodable
協(xié)議;
2.調(diào)用 JSONSerializationg
對當(dāng)前 data 進(jìn)行序列話為json數(shù)據(jù)察署;
3.通過__JSONDecoder
傳入json
數(shù)據(jù)和編碼策略創(chuàng)建decoder
對象;
4.unbox
解碼json
串峻汉,返回value
贴汪。
__JSONDecoder
私有類源碼(初始化方法):
init(referencing container: Any, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
//內(nèi)部類 _JSONDecodingStorage
self.storage = _JSONDecodingStorage()
//存放要解碼的數(shù)據(jù)
self.storage.push(container: container)
self.codingPath = codingPath
self.options = options
}
_JSONDecodingStorage
本身用數(shù)組存放數(shù)據(jù)脐往,是一個容器
private struct _JSONDecodingStorage {
// MARK: Properties
/// The container stack.
/// Elements may be any one of the JSON types (NSNull, NSNumber, String, Array, [String : Any]).
private(set) var containers: [Any] = []
// MARK: - Initialization
/// Initializes `self` with no containers.
init() {}
// MARK: - Modifying the Stack
var count: Int {
return self.containers.count
}
var topContainer: Any {
precondition(!self.containers.isEmpty, "Empty container stack.")
return self.containers.last!
}
mutating func push(container: __owned Any) {
self.containers.append(container)
}
mutating func popContainer() {
precondition(!self.containers.isEmpty, "Empty container stack.")
self.containers.removeLast()
}
}
unbox
源碼:
func unbox<T : Decodable>(_ value: Any, as type: T.Type) throws -> T? {
return try unbox_(value, as: type) as? T
}
func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
//日期格式
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)
}
}
分析下日期格式的處理
這也就是我們設(shè)置不同的日期處理策略能夠正常解析的原因扳埂。不同的策略走不同的分支解析业簿。
_JSONStringDictionaryDecodableMarker
:
#if arch(i386) || arch(arm)
internal protocol _JSONStringDictionaryDecodableMarker {
static var elementType: Decodable.Type { get }
}
#else
private protocol _JSONStringDictionaryDecodableMarker {
static var elementType: Decodable.Type { get }
}
#endif
extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String, Value: Decodable {
static var elementType: Decodable.Type { return Value.self }
}
- 字典擴展遵循了這個協(xié)議,限制條件:
Key == String, Value: Decodable
阳懂。
_JSONStringDictionaryDecodableMarker
的unbox
:
image.png
是一個遞歸調(diào)用梅尤。unbox_
就是最開始的區(qū)分類型(Date
,Data
……)的方法岩调。
看一個例子:
struct Hotpot: Codable {
var name: String
var age: Double
}
let jsonString = """
{
"name" : "hotpot",
"age" : 18
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result)
對應(yīng)的SIL
代碼:
- 解碼對應(yīng)的
key
值通過CodingKeys
去找巷燥,decodable
約束的。 -
decode
方法誊辉。 -
encode
方法矾湃。
init
方法源碼中是:
return try type.init(from: self)
傳入的decoder
是self
,這里的self
是JSONDecoder
堕澄。
init(from
SIL
的實現(xiàn):
查看
_ JSONDecoder
源碼確實實現(xiàn)了Decoder
協(xié)議sil
中調(diào)用的是_JSONDecoder
實現(xiàn)的container
方法:這里返回的是
KeyedDecodingContainer(container)
對象邀跃。KeyedDecodingContainer
內(nèi)容:可以看到有對應(yīng)每種類型的
decode
方法,意味著會根據(jù)類型匹配對應(yīng)的方法解碼蛙紫。這么多方法蘋果其實是用內(nèi)部的一個工具生成的拍屑。具體是用源碼中的Codable.swift.gyb
文件。通過Codable.swift.gyb
生成Codable.swift
源文件坑傅。Codable.swift.gyb
文件內(nèi)容:集合中放了可編解碼類型僵驰,
%%
代表語句的開始和結(jié)束。通過python
控制的唁毒。相當(dāng)于模板文件蒜茴。自己實現(xiàn)下
init
方法:
struct Hotpot: Codable {
var name: String
var age: Double
//_JSONDecoder,返回container
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(Double.self, forKey: .age)
}
}
可以看到對應(yīng)的CodingKeys
和decode
正是系統(tǒng)生成調(diào)用的方法,CodingKeys
系統(tǒng)幫我們生成了浆西,這也就是我們可以在CodingKeys
中操作key
不一致的原因粉私。
那么如果繼承自OC
的類呢?
HPOCModel
是一個OC
類:
@interface HPOCModel : NSObject
@property (nonatomic, assign) double height;
@end
@implementation HPOCModel
@end
class Hotpot:HPOCModel, Decodable {
var name: String
var age: Double
private enum CodingKeys: String,CodingKey{
case name
case age
case height
}
//_JSONDecoder,返回container
required 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(Double.self, forKey: .age)
super.init()
self.height = try container.decode(Double.self, forKey: .height)
}
}
let jsonString = """
{
"name" : "hotpot",
"age" : 18,
"height":1.85
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
let result = try decoder.decode(Hotpot.self, from: jsonData!)
print(result.height)
1.85
這個時候init
方法就要我們自己去實現(xiàn)了近零。
整個流程:
JSONEncoder
class Hotpot: Codable {
var name: String?
var age: Int?
}
class Cat: Hotpot {
var height: Double?
}
let cat = Cat()
cat.name = "cat"
cat.age = 18
cat.height = 1.85
let enCoder = JSONEncoder()
let jsonData = try enCoder.encode(cat)
let jsonStr = String(data: jsonData, encoding: .utf8)
print(jsonStr)
Optional("{\"name\":\"cat\",\"age\":18}")
源碼分析:
encode
方法:
open func encode<T : Encodable>(_ value: T) throws -> Data {
let encoder = _JSONEncoder(options: self.options)
//先box包裝成string
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)
//返回data
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))
}
}
- 在這里真正處理數(shù)據(jù)的是
_JSONEncoder
诺核; -
box
根據(jù)不同數(shù)據(jù)類型,把value
包裝成對應(yīng)的數(shù)據(jù)類型久信; -
JSONSerialization
來返回data數(shù)據(jù)窖杀。
_JSONEncoder
:
//實現(xiàn)Encoder的方法
public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
// If an existing keyed container was already requested, return that one.
let topContainer: NSMutableDictionary
if self.canEncodeNewValue {
// We haven't yet pushed a container at this level; do so here.
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)
//返回KeyedEncodingContainer,最終KeyedEncodingContainer encode根據(jù)數(shù)據(jù)類型編碼數(shù)據(jù)
return KeyedEncodingContainer(container)
}
-
_JSONEncoder
遵循Encoder
協(xié)議實現(xiàn)了container
方法裙士,最終KeyedEncodingContainer
提供對應(yīng)數(shù)據(jù)類型encode
方法編碼數(shù)據(jù)入客。
box_
:
我們自定義數(shù)據(jù)類型會走到
value.encode
,參數(shù)self
就是_JSONEcoder
。正好的解碼過程相反痊项。最終都會都到
value is _JSONStringDictionaryEncodableMarker
_JSONStringDictionaryEncodableMarker
:在最開始的例子中锅风,Cat
中的height
并沒有被編碼出來。
先看下繼承和協(xié)議遵循關(guān)系:
那么
Hotpot
遵循了Codable
協(xié)議鞍泉,編譯器默認(rèn)生成了encode
方法皱埠,在調(diào)用過程中由于Cat
中沒有對應(yīng)encode
方法,所以會去父類中找咖驮。所以編碼結(jié)果中沒有height
边器。SIL
驗證下:
class Hotpot : 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: Hotpot.CodingKeys, _ b: Hotpot.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
}
@_inheritsConvenienceInitializers class Cat : Hotpot {
@_hasStorage @_hasInitialValue var height: Double? { get set }
@objc deinit
override init()
required init(from decoder: Decoder) throws
}
Hotpot
中有init
和encode
方法。而在Cat
中編譯器覆蓋了init(from decoder: Decoder)
方法托修,而沒有覆蓋encode
忘巧。這也就解釋了解碼能夠無縫對接,編碼不行的原因睦刃。
那么我們需要在子類中重寫encode
方法:
class Hotpot: Codable {
var name: String?
var age: Int?
}
class Cat: Hotpot {
var height: Double?
enum CodingKeys: String,CodingKey {
case height
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(height, forKey: .height)
try super.encode(to: encoder)
}
}
let cat = Cat()
cat.name = "cat"
cat.age = 18
cat.height = 1.8
let enCoder = JSONEncoder()
let jsonData = try enCoder.encode(cat)
let jsonStr = String(data: jsonData, encoding: .utf8)
Optional("{\"name\":\"cat\",\"age\":18,\"height\":1.8}")
當(dāng)然這里要注意數(shù)據(jù)的精度問題砚嘴,前面用法里面已經(jīng)說過了。
有一個小的點try super.encode(to: encoder)
如果用自己的編碼器呢涩拙?
try super.encode(to: container.superEncoder())
Optional("{\"super\":{\"name\":\"cat\",\"age\":18},\"height\":1.8}")
編碼值中多了super
际长。所以這里最好就傳當(dāng)前類的encoder
。否則多次編解碼會錯誤解析不到數(shù)據(jù)兴泥。
對于上面編碼后的值工育,再解碼看看:
let cat2 = try JSONDecoder().decode(Cat.self, from: jsonData)
Optional("{\"name\":\"cat\",\"age\":18,\"height\":1.8}")
SwiftProtocol.Cat
(lldb) po cat2.name
? Optional<String>
- some : "cat"
(lldb) po cat2.age
? Optional<Int>
- some : 18
(lldb) po cat2.height
nil
可以看到height
沒有解析出來。這和前面的結(jié)論一樣搓彻,看下SIL
:
在最后確實調(diào)用了
Hotpot
的decode
方法如绸,所以父類中的數(shù)據(jù)解析沒有問題,但是height
在SIL
中只有創(chuàng)建沒有賦值旭贬。沒有創(chuàng)建Cat
自己本身的container
怔接。再看下和Hotpot
的區(qū)別:可以看到區(qū)別是僅僅
Cat
中根本沒有創(chuàng)建Container
,僅是調(diào)用了父類的decode
方法稀轨,這也是為什么沒有解析出來自己的屬性蜕提。(Swift源碼中繼承后在
vscode
中調(diào)試報錯,只能編譯一份Xcode源碼再調(diào)試了靶端,后面再補充吧).源碼調(diào)試待補充
我們自己實現(xiàn)
cat
的init
方法看下sil
代碼:
class Hotpot:Codable {
var name: String?
var age: Int?
}
class Cat: Hotpot {
var height: Double?
enum CodingKeys: String, CodingKey {
case height
}
required init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
self.height = try container.decode(Double.self, forKey: .height)
try! super.init(from: decoder)
}
}
let jsonString = "{\"name\" : \"hotpot\",\"age\" : 18,\"height\":1.8}"
let cat2 = try JSONDecoder().decode(Cat.self, from: jsonString.data(using: .utf8)!)
print(cat2)
SwiftProtocol.Cat
(lldb) po cat2.age
? Optional<Int>
- some : 18
(lldb) po cat2.height
? Optional<Double>
- some : 1.8
這樣就能正常解析了,注釋掉不必要的代碼看下SIL
:
-
Cat
創(chuàng)建了自己的container
; -
bb1
中對height
進(jìn)行了解析賦值凛膏; -
bb2
分支中調(diào)用了Hotpot
的init
方法杨名。
編解碼多態(tài)中的應(yīng)用
protocol Hotpot: Codable {
var age: String { get set }
var name: String { get set }
}
struct Cat: Hotpot {
var age: String
var name: String
}
struct Dog: Hotpot {
var age: String
var name: String
}
struct Animal: Codable{
var animals: [Hotpot]
var desc: String
enum CodingKeys: String, CodingKey {
case animals
case desc
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(animals, forKey: .animals)
try container.encode(desc, forKey: .desc)
}
}
上面的例子中,Animal
中animals
存儲的是遵循Hotpot
協(xié)議的屬性:
看到直接報錯猖毫,有自定義類型就需要我們自己實現(xiàn)
init
和decode
方法了台谍。并且animals
不能解析,需要各自的類型(Cat
吁断、Dog
)自己去實現(xiàn)相應(yīng)的init
和decode
方法趁蕊。這在類型比較少的時候還能接受坞生,類型多了的情況下呢?那么可以用一個中間層
HotpotBox
去專門做解析:
protocol Hotpot {
var age: Int { get set }
var name: String { get set }
}
struct HotpotBox: Hotpot, Codable {
var age: Int
var name: String
init(_ hotpot: Hotpot) {
self.age = hotpot.age
self.name = hotpot.name
}
}
struct Cat: Hotpot {
var age: Int
var name: String
}
struct Dog: Hotpot {
var age: Int
var name: String
}
struct Animal: Codable{
var animals: [HotpotBox]
var desc: String
}
調(diào)用
let hotpots: [Hotpot] = [Cat(age: 18, name: "cat"),Dog(age: 28, name: "dog")]
//對hotpots數(shù)組中的集合執(zhí)行HotpotBox.init是己,也就是用hotpots初始化animals
let animal = Animal(animals: hotpots.map(HotpotBox.init), desc: "Animal")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(animal)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
輸出
{
"animals" : [
{
"age" : 18,
"name" : "cat"
},
{
"age" : 28,
"name" : "dog"
}
],
"desc" : "Animal"
}
再decode
一下:
let animal1: Animal = try JSONDecoder().decode(Animal.self, from: jsonData)
print(animal1)
輸出:
Animal(animals: [SwiftProtocol.HotpotBox(age: 18, name: "cat"), SwiftProtocol.HotpotBox(age: 28, name: "dog")], desc: "Animal")
可以看到這里輸出的是HotpotBox
,如果要還原當(dāng)前的類型信息呢任柜?
1.可以寫一個對應(yīng)的unBox
來還原數(shù)據(jù):
struct Cat: Hotpot {
var age: Int
var name: String
static func unBox(_ value: HotpotBox) -> Cat {
var cat = Cat(age: value.age, name: value.name)
return cat
}
}
[SwiftProtocol.Cat(age: 18, name: "cat"), SwiftProtocol.Cat(age: 28, name: "dog")]
2.編碼過程中將類型信息編碼進(jìn)去卒废。
enum HotpotType: String, Codable {
case cat
case dog
var metadata: Hotpot.Type {
switch self {
case .cat:
return Cat.self
case .dog:
return Dog.self
}
}
}
protocol Hotpot: Codable {
static var type: HotpotType { get }//計算屬性,編解碼過程中不會被編碼進(jìn)去宙地。
var age: Int { get set }
var name: String { get set }
}
struct HotpotBox: Codable {
var hotpot: Hotpot
init(_ hotpot: Hotpot) {
self.hotpot = hotpot
}
private enum CodingKeys: String, CodingKey {
case type
case hotpot
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(HotpotType.self, forKey: .type)
self.hotpot = try type.metadata.init(from: container.superDecoder(forKey: .hotpot))
}
func encode(to encoder: Encoder) throws {
var container = try encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: hotpot).type, forKey: .type)
try hotpot.encode(to: container.superEncoder(forKey: .hotpot))
}
}
struct Cat: Hotpot {
static var type: HotpotType = .cat
var age: Int
var name: String
}
struct Dog: Hotpot {
static var type: HotpotType {
.dog
}
var age: Int
var name: String
}
struct Animal: Codable {
var animals: [HotpotBox]
var desc: String
}
let hotpots: [Hotpot] = [Cat(age: 18, name: "cat"),Dog(age: 28, name: "dog")]
//對hotpots數(shù)組中的集合執(zhí)行HotpotBox.init摔认,也就是用hotpots初始化animals
let animal = Animal(animals: hotpots.map(HotpotBox.init), desc: "Animal")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(animal)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
let animal1: Animal = try JSONDecoder().decode(Animal.self, from: jsonData)
print(animal1)
輸出
{
"animals" : [
{
"type" : "cat",
"hotpot" : {
"age" : 18,
"name" : "cat"
}
},
{
"type" : "dog",
"hotpot" : {
"age" : 28,
"name" : "dog"
}
}
],
"desc" : "Animal"
}
Animal(animals: [SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Cat(age: 18, name: "cat")), SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Dog(age: 28, name: "dog"))], desc: "Animal")
這個時候看到有無用信息,再改造下:
self.hotpot = try type.metadata.init(from: container.superDecoder(forKey: .hotpot))
改為
self.hotpot = try type.metadata.init(from: decoder)
{
"animals" : [
{
"type" : "cat",
"age" : 18,
"name" : "cat"
},
{
"type" : "dog",
"age" : 28,
"name" : "dog"
}
],
"desc" : "Animal"
}
Animal(animals: [SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Cat(age: 18, name: "cat")), SwiftProtocol.HotpotBox(hotpot: SwiftProtocol.Dog(age: 28, name: "dog"))], desc: "Animal")
如果不想要type
func encode(to encoder: Encoder) throws {
var container = try encoder.container(keyedBy: CodingKeys.self)
// try container.encode(type(of: hotpot).type, forKey: .type)
try hotpot.encode(to: encoder)
}
去掉就好了宅粥,不過在解碼的時候就沒有type
相關(guān)信息了参袱。需要根據(jù)需求靈活處理
{
"animals" : [
{
"age" : 18,
"name" : "cat"
},
{
"age" : 28,
"name" : "dog"
}
],
"desc" : "Animal"
}
另外一種方式
protocol Meta: Codable {
associatedtype Element
static func metatype(for typeString: String) -> Self
var type: Decodable.Type { get }
}
struct MetaObject<M: Meta>: 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 HotpotType: String, Meta {
typealias Element = Hotpot
case cat = "Cat"
case dog = "Dog"
static func metatype(for typeString: String) -> HotpotType {
guard let metatype = self.init(rawValue: typeString) else {
fatalError()
}
return metatype
}
var type: Decodable.Type {
switch self {
case .cat:
return Cat.self
case .dog:
return Dog.self
}
}
}
class Hotpot: Codable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
class Cat: Hotpot {
var height: Double
init(name: String, age: Int, height: Double) {
self.height = height
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
height = try container.decode(Double.self, forKey: .height)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(height, forKey: .height)
try super.encode(to: encoder)
}
enum CodingKeys: String, CodingKey {
case height
}
}
class Dog: Hotpot {
var weight: Double
init(name: String, age: Int, weight: Double) {
self.weight = weight
super.init(name: name, age: age)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
weight = try container.decode(Double.self, forKey: .weight)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(weight, forKey: .weight)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
enum CodingKeys: String, CodingKey {
case weight
}
}
let hotpot: Hotpot = Cat(name: "cat", age: 18, height: 1.8)
let jsonData = try JSONEncoder().encode(MetaObject<HotpotType>(hotpot))
if let str = String(data: jsonData, encoding: .utf8) {
print(str)
}
let decode: MetaObject<HotpotType> = try JSONDecoder().decode(MetaObject<HotpotType>.self, from: jsonData)
print(decode)
{"metatype":"Cat","object":{"name":"cat","age":18,"height":1.8}}
MetaObject<HotpotType>(object: SwiftProtocol.Cat)
- 中間層
- 記錄metadata
- Type
在編解碼過程中遇到多態(tài)的方式,可以通過中間層去解決處理秽梅。
SwiftUI中Codable
當(dāng)我們在SwiftUI
中定義如下代碼會直接報錯(ObservableObject
和Published
可以實現(xiàn)組件數(shù)據(jù)一致性抹蚀,而且可以自動更新):
class Hotpot: ObservableObject, Codable {
@Published var name = "cat"
}
這實際上是
Swift
的一項基本原則,我們不能說var names: Set
风纠,因為Set
是通用類型,必須創(chuàng)建Set<String>
的實例况鸣。Published
也是同理,不能自己創(chuàng)建Published
的實例竹观,而是創(chuàng)建一個Published<String>
的實例镐捧。這時候就需要我們自己編寫編解碼操作了:
class Hotpot: ObservableObject, Codable {
@Published var name = "cat"
enum CodingKeys: CodingKey {
case name
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
}
}
比較
Codable
:繼承多態(tài)模式下需要自己編寫編解碼。
HandyJSON
:內(nèi)存賦值的方式進(jìn)行編解碼操作臭增,依賴metadata
懂酱。如果metadata
變化后會出現(xiàn)問題,遷移成本比較高誊抛。
SwiftyJSON
:需要使用下標(biāo)的方式取值列牺。
ObjectMapper
:需要手動對每一個屬性提供映射關(guān)系。
如果項目中數(shù)據(jù)模型繼承和多態(tài)比較少建議直接用Codable
拗窃,否則就用HandyJSON
吧瞎领。相對來說Codable
效率更高。
具體的對比以后再補充吧随夸。
參考:
https://zhuanlan.zhihu.com/p/50043306
http://www.reibang.com/p/f4b3dce8bd6f
http://www.reibang.com/p/1f194f09599a
http://www.reibang.com/p/bdd9c012df15
http://www.reibang.com/p/6db40c4c0ff9
http://www.reibang.com/p/5dab5664a621?utm_campaign
http://www.reibang.com/p/bf56a74323fa
http://www.reibang.com/p/0088b62f698a
enum解析
解析框架對比
Double精度問題
NSDecimalNumber