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:)
函數啦!🎉🎉🎉
以爲文章還不錯的話能夠關注一下個人博客