1 什么是Codable协议,解决了什么问题
Codable 是 Swift 4.0 推出的,可以将 JSON 数据自动解析为model的一个协议。旨在取代 NSCoding 协议,支持结构体、枚举和类, typealias Codable = Decodable & Encodable
2 怎么使用Codable协议
参见: Swift4中Codable的使用(一):www.jianshu.com/p/5dab5664a… 这篇文章还介绍了一些特殊情况的使用,比如
- 数组作为JSON根对象
- 纯数组中的对象带有唯一Key
具体可查看文章中的Demo
struct Student: Codable {
let name: String
let age: Int
}
当我们像上面这样定义Student时,编译器会自动生成实现Codable协议的方法
public protocol Encodable {
public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
public init(from decoder: Decoder) throws
}
public typealias Codable = Decodable & Encodable
实际上,我们也可以直接实现Codable协议,也就是自己实现:
public protocol Encodable {
public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
public init(from decoder: Decoder) throws
}
这篇文章:Swift4中Codable的使用(二)www.jianshu.com/p/6db40c4c0… 有详细的讲解和Demo
对这篇文章的内容熟悉之后,才有可能看懂后面的内容
对于 处理带有派生关系的模型 或 处理key个数不确定的json 这篇文章 Swift4中Codable的使用(三)有详细介绍
3 直接使用Codable面临的问题
通过2,我们了解了Codable的基础使用,但是我们直接使用Codable的话,有以下问题:
如果接口返回的数据少了一个key,就会全部解析失败; 或者把属性定义为可选的,如果定义为可选的,使用时都要先解包,麻烦; 没有值的时候怎么设置默认值
4 直接使用Codable面临的问题的解决方案:
对于上面的问题 这篇文章: 使用 Property Wrapper 为 Codable 解码设定默认值 : onevcat.com/2020/11/cod… (解决方案1) 用属性包装 Property Wrapper 算是解决了一些痛点,但是没有全部解决默认值的问题,这个方法里面的默认值只是对数据类型的默认值, 使用者不能自定义默认值
2021 年了,Swift 的 JSON-Model 转换还能有什么新花样: zhuanlan.zhihu.com/p/351928579… (解决方案2)
解决方案2 实现了的功能相对多一些,但是代价就是自己实现了解码,而且属性包装器是一个Class,如果在Struct中使用这个属性包装器会有点问题,Struct是值类型,Class是引用类型
还有一个问题是定义属性的时候无法定义为 Any, Any不支持Codable
Swift是时候使用Codable了: juejin.cn/post/716874… 这篇文章对 方案2 进行了优化,实现了可以把属性定义为Any
讨论: 为什么方案1 不能定义默认值,方案2 可以呢?
下面的代码对方案1的完善
public protocol DecodableDefaultSource {
associatedtype Value: Decodable
static var defaultValue: Value { get }
}
public enum DecodableDefault {}
public extension DecodableDefault {
@propertyWrapper
struct Wrapper<Source: DecodableDefaultSource> {
public init() {
}
public typealias Value = Source.Value
public var wrappedValue = Source.defaultValue
}
}
extension DecodableDefault.Wrapper: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
}
public extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
return try decodeIfPresent(type, forKey: key) ?? .init()
}
}
public extension DecodableDefault {
typealias Source = DecodableDefaultSource
typealias List = Decodable & ExpressibleByArrayLiteral
typealias Map = Decodable & ExpressibleByDictionaryLiteral
enum Sources {
public enum True: Source { public static var defaultValue: Bool { true } }
public enum False: Source { public static var defaultValue: Bool { false } }
public enum EmptyString: Source { public static var defaultValue: String { "" } }
public enum EmptyList<T: List>: Source { public static var defaultValue: T { [] } }
public enum EmptyMap<T: Map>: Source { public static var defaultValue: T { [:] } }
public enum Zero: Source { public static var defaultValue: Int { 0 } }
}
}
public extension DecodableDefault {
typealias True = Wrapper<Sources.True>
typealias False = Wrapper<Sources.False>
typealias EmptyString = Wrapper<Sources.EmptyString>
typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
typealias Zero = Wrapper<Sources.Zero>
}
extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}
extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
要使用方案1, 只需这样定义一个model即可
struct ArticleModel: Codable {
@DecodableDefault.Zero var title
@DecodableDefault.EmptyString var created_at_str
}
使用
let jsonString1123 = """
{
"title" : 22,
"created_at_str111" : "2--",
}
"""
let jsonData1123 = jsonString1123.data(using: .utf8)
let decoder1123 = JSONDecoder()
if let jsonData1123 = jsonData1123,
let result = try? decoder1123.decode(ArticleModel.self, from: jsonData1123) {
print(result)
} else {
print("解析失败")
}
我们来分析一下解析的流程
ArticleModel 中定义的title 和created_at_str本质是一个struct,所以 解析的时候会调用Wrapper的解析方法,也就是会调用到
##1
extension DecodableDefault.Wrapper: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
}
但是调用上面这个方法的前提是 json数据中,有modle的key; 如果key不存在,就不会调用上面的方法,而是直接报错,整个modle都无法解析,为了解决这个问题我们重写了下面这个方法
##2
public extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
return try decodeIfPresent(type, forKey: key) ?? .init()
}
}
也就是如果key 不存在,我们就返回 DecodableDefault.Wrapper.init(),在init的时候没有办法获取定义时的默认值,所以无法实现随意定义默认值
注意:调用的时候是先调用 ##2 ,在 ##2 中的 try decodeIfPresent(type, forKey: key) 方法中,如果key存在, 会触发调用##1;注意不是执行完decodeIfPresent(type, forKey: key),再调用##1,而是在decodeIfPresent(type, forKey: key)执行的过程中会调用##1。 如果key不存在就不会调用##1了,decodeIfPresent(type, forKey: key)也会返回nil,
要使用方案2, 只需这样定义一个model即可
struct TestStruct: Equatable, ExAutoCodable {
@ExCodable("shantt") // 字段和属性同名可以省掉字段名和括号,但 `@ExCodable` 还是没办法省掉
var int: Int = 29
@ExCodable("string", "str", "s", "nested.string") // 支持多个 key 以及嵌套 key 可以这样写
var string: String? = "sadfj"
}
注意 我们在使用方案1的时候是这么定义的 struct ArticleModel: Codable ,ArticleModel只遵守了系统的Codable协议,而使用 方案2 的时候 struct TestStruct: Equatable, ExAutoCodable , 遵守了ExAutoCodable协议,这协议就是自定义编解码的。
个人认为一般的业务使用 方案1 就够了,
相关文档
- 关于Codable协议处理数据实体属性缺省值问题 : juejin.cn/post/695882…
- 有关Swift Codable解析成Dictionary<String, Any>的一些事 : juejin.cn/post/702841…
- 使用 Property Wrapper 为 Codable 解码设定默认值 : onevcat.com/2020/11/cod…
- Codable保姆级攻略 : juejin.cn/post/712266…
- Swift是时候使用Codable了 : juejin.cn/post/716874…
- Swift协议Codable底层探索及应用 : juejin.cn/post/693838…
- 利用Swift的反射将Struct转换成Dictionary :hisoka0917.github.io/swift/2018/…
- Swift - Codable 使用小记 : yupeng.fun/2021/06/08/…
- 【译】Swift&JSON 从入门到精通 :juejin.cn/post/704487…
- Encoding and Decoding in Swift : www.kodeco.com/3418439-enc…
- Swift4中Codable的使用(一):www.jianshu.com/p/5dab5664a…
- Swift4中Codable的使用(二):www.jianshu.com/p/6db40c4c0…
- Swift4中Codable的使用(三):www.jianshu.com/p/bf56a7432…
- 利用 Decoder 來解析 JSON 要懂的那些方法 : franksios.medium.com/%E5%88%A9%E…
- iOS Swift 原生 字典数组转模型 JSONDecoder 对象存储 NSKeyedArchiver : www.jianshu.com/p/0fa4e0ee0…
- Swift 中的 JSON 反序列化: zhuanlan.zhihu.com/p/541854557