Codable使用
Codable
协议是Swift4.0
之后出来的编解码协议,在Json 转 model 时非常方便,如下所示
// 1
struct LYPerson:Codable {
var name: String?
var age: Int?
}
// 2
let dict: [String : Any] = ["name": "Jack", "age": 10]
let decoder = JSONDecoder()
let data = try JSONSerialization.data(withJSONObject: dict, options: .fragmentsAllowed)
let p = try decoder.decode(LYPerson.self, from: data)
- 1,自定义对象,遵守
Codable
协议。 - 2,初始化
JSONDecoder
对象,将 json数据进行序列化,然后进行解码。
这样我们就可以将 dict里面的键值对映射到 LYPerson
对象中了。
源码分析
Decoder
我们打开编译好的swift-foundation(5.3.2版本) 先看下codable
的定义
public typealias Codable = Decodable & Encodable
codable
是 Decodable
和Encodable
的别名
public protocol Decodable {
/// Creates a new instance by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
init(from decoder: Decoder) throws
}
Decodable
协议就只有一个 init(from decoder:Decode)
方法
我们在解码的时候:
1,初始化了一个JSONDecoder
对象。
2,调用 decode<T : Decodable>(_ type: T.Type, from data: Data)
方法。
接下来,我们看下decode
方法做了哪些操作。
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
1,传入的参数 为解码的Type,在本例中是LYPerson
。另一个参数为 Data
数据,即 dict。
2,对 Data
进行Json序列化
。
3,创建内部类 _JSONDecoder
,并将Json数据
和解码策略
。
4,调用 unbox
方法。
解码策略
当我们初始化JSONDecoder
对象时,如果不设置解码策略,则会采用默认的解码策略
fileprivate var options: _Options {
return _Options(dateDecodingStrategy: `dateDecodingStrategy = secondsSince1970`,
dataDecodingStrategy: dataDecodingStrategy,
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
keyDecodingStrategy: keyDecodingStrategy,
userInfo: userInfo)
}
我们以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)
// 1
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.")
}
// 2
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
// 3
case .custom(let closure):
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try closure(self)
}
}
- 1,如果时间策略是
secondsSince1970
,则会返回从 1970至今的时间戳。 - 2,如果有确定的时间格式,则返回特定格式的时间。
- 3,如果是自定义解码,则进行闭包回调。
通过设置不同的解码策略,来完成个性化的解码。
unbox
我们先看 unbox
方法的实现
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
#if DEPLOYMENT_RUNTIME_SWIFT
print(type)
// Bridging differences require us to split implementations here
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)
}
- 1,如果是
Data
,Date
,URL
类型,则根据不同的编码策略,生成一个相对应类型的实例变量。
fileprivate func unbox<T>(_ value: Any, as type: _JSONStringDictionaryDecodableMarker.Type) throws -> T? {
guard !(value is NSNull) else { return nil }
var result = [String : Any]()
guard let dict = value as? NSDictionary else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let elementType = type.elementType
for (key, value) in dict {
let key = key as! String
self.codingPath.append(_JSONKey(stringValue: key, intValue: nil))
defer { self.codingPath.removeLast() }
result[key] = try unbox_(value, as: elementType)
}
return result as? T
}
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 }
}
- 1,如果是
Dictionary
类型,则对 键值对 进行遍历,对value值进行解码。
在本例中,一开始传入的type == LYPerson
,它会调用其 init(from decoder: Decoder)
方法。
LYPerson
对象的 init(from decoder: Decoder)
方法我们没有进行声明,那它从哪里来的呢?编辑器为我们做了一些处理。
SIL分析
我们将代码转化为 SIL
struct LYPerson : Decodable & Encodable {
@_hasStorage @_hasInitialValue var name: String? { get set }
@_hasStorage @_hasInitialValue var age: Int? { get set }
init(name: String? = nil, age: Int? = nil)
init()
enum CodingKeys : CodingKey {
case name
case age
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: LYPerson.CodingKeys, _ b: LYPerson.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
和编解码方法
。初始化方法具体如下
// LYPerson.init(from:)
sil hidden @$s4main8LYPersonV4fromACs7Decoder_p_tKcfC : $@convention(method) (@in Decoder, @thin LYPerson.Type) -> (@owned LYPerson, @error Error) {
// %0 "decoder" // users: %71, %52, %12, %3
// %1 "$metatype"
bb0(%0 : $*Decoder, %1 : $@thin LYPerson.Type):
%2 = alloc_stack $LYPerson, var, name "self" // users: %43, %27, %9, %6, %53, %72, %73, %54
debug_value_addr %0 : $*Decoder, let, name "decoder", argno 1 // id: %3
debug_value undef : $Error, var, name "$error", argno 2 // id: %4
%5 = enum $Optional<String>, #Optional.none!enumelt // user: %7
%6 = struct_element_addr %2 : $*LYPerson, #LYPerson.name // user: %7
store %5 to %6 : $*Optional<String> // id: %7
%8 = enum $Optional<Int>, #Optional.none!enumelt // user: %10
%9 = struct_element_addr %2 : $*LYPerson, #LYPerson.age // user: %10
store %8 to %9 : $*Optional<Int> // id: %10
%11 = alloc_stack $KeyedDecodingContainer<LYPerson.CodingKeys>, let, name "container" // users: %48, %47, %40, %68, %67, %24, %62, %61, %16, %57
%12 = open_existential_addr immutable_access %0 : $*Decoder to $*@opened("D0F7E680-7A9A-11EB-8646-186590D5CC1F") Decoder // users: %16, %16, %15
%13 = metatype $@thin LYPerson.CodingKeys.Type
%14 = metatype $@thick LYPerson.CodingKeys.Type // user: %16
%15 = witness_method $@opened("D0F7E680-7A9A-11EB-8646-186590D5CC1F") Decoder, #Decoder.container : <Self where Self : Decoder><Key where Key : CodingKey> (Self) -> (Key.Type) throws -> KeyedDecodingContainer<Key>, %12 : $*@opened("D0F7E680-7A9A-11EB-8646-186590D5CC1F") 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: %12; user: %16
try_apply %15<@opened("D0F7E680-7A9A-11EB-8646-186590D5CC1F") Decoder, LYPerson.CodingKeys>(%11, %14, %12) : $@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: %12; id: %16
- 1 ,首先创建了一个Container:
KeyedDecodingContainer
- 2 ,然后从协议目击表中,调用Decoder协议的
container
方法
查阅源码,我们可知 _JSONDecoder
遵守 Decoder
协议
public protocol Decoder {
/// The path of coding keys taken to get to this point in decoding.
var codingPath: [CodingKey] { get }
/// Any contextual information set by the user for decoding.
var userInfo: [CodingUserInfoKey : Any] { get }
/// Returns the data stored in this decoder as represented in a container
/// keyed by the given key type.
///
/// - parameter type: The key type to use for the container.
/// - returns: A keyed decoding container view into this decoder.
/// - throws: `DecodingError.typeMismatch` if the encountered stored value is
/// not a keyed container.
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
/// Returns the data stored in this decoder as represented in a container
/// appropriate for holding values with no keys.
///
/// - returns: An unkeyed container view into this decoder.
/// - throws: `DecodingError.typeMismatch` if the encountered stored value is
/// not an unkeyed container.
func unkeyedContainer() throws -> UnkeyedDecodingContainer
/// Returns the data stored in this decoder as represented in a container
/// appropriate for holding a single primitive value.
///
/// - returns: A single value container view into this decoder.
/// - throws: `DecodingError.typeMismatch` if the encountered stored value is
/// not a single value container.
func singleValueContainer() throws -> SingleValueDecodingContainer
}
Decoder
协议中定义了三种容器:KeyedDecodingContainer
,UnkeyedDecodingContainer
,SingleValueDecodingContainer
。
- KeyedDecodingContainer 类似于字典,键值对容器,键值是强类型。
- UnkeyedDecodingContainer 类似于数组,连续值容器,没有键值。
- SingleValueDecodingContainer 基础数据类型容器。
在 KeyedDecodingContainer中,对基本数据类型进行解码赋值操作
自定义解码方法
struct LYPerson: Codable {
var name: String?
var age: Int?
enum CodingKeys : CodingKey {
case name
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)
}
}
let dict: [String : Any] = ["name": "Jack", "age": 10]
let decoder = JSONDecoder()
let data = try JSONSerialization.data(withJSONObject: dict, options: .fragmentsAllowed)
let p = try decoder.decode(LYPerson.self, from: data)
print(p.age)
1,定义 CodingKeys
,声明解码的key
。
2,通过JSONDecoder
,获取 KeyedDecodingContainer
。
3,通过container对象对基本数据类型进行解码赋值。