或许你并不须要重写 init(from:) 方法

Codable 做为 Swift 的特性之一也是很注重安全,也很严谨,但它对于“严谨”和“安全”的定义不必定跟别的语言同样,这就致使了它在实际使用时总会有这样那样的磕磕绊绊,咱们不得不重写 init 方法去让它跟外部环境融洽地共存。最近在工做中这样的事情发生多了,我也就不得不想办法去解决它。git

严格的类型解析

最开始遇到了第一个问题就是 Bool 的解析,咱们后端的接口习惯使用 01 整数去表达布尔值,解析失败以后,我第一感受是这会不会是个 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

还有一个比较棘手的问题,URLinit?(string:) 在传入空字符串的时候会初始化失败,因此在把空字符串解析为 URL 的时候会直接中断整个解析而后抛出错误,还有一个就是数组内部存在 null 元素的时候,若是 Array 的元素不声明为 Optional 的话也是会中断解析。后端

Swizzle 掉 decode 方法

比起从新自定义一个 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 内部开始查找,那就尝试一下:安全

成功了,那么就回到咱们最初的目的,把 URLBool 也重载掉:框架

而且这种重载的方法是用的是直接派发,因此咱们能够控制这个函数的做用范围:函数

// 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:) 函数啦!🎉🎉🎉

以为文章还不错的话能够关注一下个人博客

相关文章
相关标签/搜索