Codable 做为 Swift 的特性之一也是很注重安全,也很严谨,但它对于“严谨”和“安全”的定义不必定跟别的语言同样,这就致使了它在实际使用时总会有这样那样的磕磕绊绊,咱们不得不重写 init 方法去让它跟外部环境融洽地共存。最近在工做中这样的事情发生多了,我也就不得不想办法去解决它。git
最开始遇到了第一个问题就是 Bool
的解析,咱们后端的接口习惯使用 0
跟 1
整数去表达布尔值,解析失败以后,我第一感受是这会不会是个 bug,因此去翻了一下 JSONDecoder
的源码:github
func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? {
...
if let number = value as? NSNumber {
if number === kCFBooleanTrue as NSNumber {
return true
} else if number === kCFBooleanFalse as NSNumber {
return false
}
}
...
}
复制代码
若是把 ===
改为 ==
就能够很好地解决个人问题,我原本还很天真得觉得这真的是个 bug,但在 Twitter 上向开发组的人求证以后,他们表示代码并无错,就是这么设计的,Boolean 就是 Boolean,Int 就是 Int,不该该混到一块儿用。swift
还有一个比较棘手的问题,URL
的 init?(string:)
在传入空字符串的时候会初始化失败,因此在把空字符串解析为 URL
的时候会直接中断整个解析而后抛出错误,还有一个就是数组内部存在 null
元素的时候,若是 Array
的元素不声明为 Optional
的话也是会中断解析。后端
比起从新自定义一个 Decoder 来讲,若是可以 swizzle 掉 decode 方法,直接控制 decode 行为会更加方便。实际上咱们真的能够作到,Codable 的原理是自动代码生成,严格来讲,它其实不算是编译的一部分:数组
struct Foo: Codable {
var bar: Int?
// <--自动生成的部分
init(from decoder: Decoder) throws {
let container = decoder.container(keyedBy: CodingKeys.self)
bar = container.decodeIfPresent(Int.self, forKey: .bar)
}
// 自动生成的部分-->
}
复制代码
而且 decodeIfPresent
方法是在 Foundation 框架里的,那么咱们能不能在咱们的 Module 里也写一个 decodeIfPresent
方法重载掉它呢?由于若是方法是在 extension 里声明并实现的话,方法会优先从 Module 内部开始查找,那就尝试一下:安全
成功了,那么就回到咱们最初的目的,把 URL
和 Bool
也重载掉:框架
而且这种重载的方法是用的是直接派发,因此咱们能够控制这个函数的做用范围:函数
// A 文件
extension KeyedDecodingContainer {
fileprivate func decodeIfPresent(_ type: Int.Type, forKey key: CodingKey) -> Int? { ... }
}
// B 文件
// 这里不会调用到 A 文件里的方法
let b = container.decodeIfPresent(Int.self, forKey: key)
复制代码
甚至咱们能够在 Module 内重载一遍,应对个别特殊状况能够在文件里再重载一遍,达到最佳的灵活度,从某种程度上来讲,我认为这甚至是比 Objective-C 的消息机制更加灵活的一种函数声明机制,并且它的影响范围是有限的,不容易对外部模块形成破坏(别声明为 open
或者 public
就不会有问题)。spa
我对于 Twitter 上 Swift 开发团队的成员发的一条推印象特别深,他说其实 Swift 也有 Selector 和 IMP 的机制,只不过这个方法选择的过程是在编译时去完成,而并不是在运行时去完成的。经过了解方法选择的规则,就能够作到相似于 Swizzle 的效果,这也是 Swift 重载机制有趣并且复杂的地方。设计
如今你们能够经过这种方法去重构掉项目里那些多余的 init(from:)
函数啦!🎉🎉🎉
以为文章还不错的话能够关注一下个人博客