或許你並不須要重寫 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:) 函數啦!🎉🎉🎉

以爲文章還不錯的話能夠關注一下個人博客

相關文章
相關標籤/搜索