- 與許多其它語言相比,使 Swift 更加安全,更不易出錯的原因之一是其先進的(并且在某種程度上是不容忍的)型別系統,這是一種語言功能,有時可能會給人留下深刻的印象,使我們的作業效率提高很多,而有時卻令人沮喪,
- 在 Swift 中處理泛型時,可能發生的一種情況,以及通常如何使用基于閉包的型別擦除技術來解決這種情況,
- 假設要撰寫一個類,可以通過網路加載模型,由于不想為應用程式中的每個模型都復制此類,因此選擇使其成為泛型類,如下所示:
class ModelLoader<T: Unboxable & Requestable> {
func load(completionHandler: (Result<T>) -> Void) {
networkService.loadData(from: T.requestURL) { data in
do {
try completionHandler(.success(unbox(data: data)))
} catch {
let error = ModelLoadingError.unboxingFailed(error)
completionHandler(.error(error))
}
}
}
}
- 到目前為止,有了一個 ModelLoader,它能夠加載任何模型(只要它是遵守 Unboxable 協議的),并且能夠向我們提供 requestURL,但是,我們還希望啟用使用此模型加載器的代碼易于測驗,因此將其 API 提取到一個協議中:
protocol ModelLoading {
associatedtype Model
func load(completionHandler: (Result<Model>) -> Void)
}
- 這和依賴注入一起能夠輕松地在測驗中模擬我們的模型加載 API,但這帶來了一些復雜性,在每當要使用此 API 時,都必須將其稱為協議 ModelLoading,該協議具有相關的型別要求,這意味著僅參考 ModelLoading 是不夠的,因為在沒有更多資訊的情況下編譯器無法推斷其關聯型別,因此,嘗試執行以下操作:
class ViewController: UIViewController {
init(modelLoader: ModelLoading) {
...
}
}
- 會提示如下錯誤:
Protocol 'ModelLoading' can only be used as a generic constraint because it as Self or associated type requirements
- 但不用擔心,我們可以通過使用泛型輕松擺脫此錯誤,強制執行符合 Modelloading 的具體型別將由 API 用戶指定,并且它將加載期待的模型,像這樣:
class ViewController: UIViewController {
init<T: ModelLoading>(modelLoader: T) where T.Model == MyModel {
...
}
}
- 這是有效的,但由于我們還希望在視圖控制器中參考模型加載程式,需要能夠指定屬性的型別, T 只在初始化程式的背景關系中知道,因此無法定義 T 型別的屬性,除非使視圖控制器類本身成為泛型,相反,讓我們使用型別擦除,能夠保存某種 T 的參考,而無需實際使用其型別,這可以通過創建擦除型別的類,例如“包裝類”來完成:
class AnyModelLoader<T>: ModelLoading {
typealias CompletionHandler = (Result<T>) -> Void
private let loadingClosure: (CompletionHandler) -> Void
init<L: ModelLoading>(loader: L) where L.Model == T {
loadingClosure = loader.load
}
func load(completionHandler: CompletionHandler) {
loadingClosure(completionHandler)
}
}
- 以上這種型別擦除技術,其實在 Swift 標準庫中也很常用,例如在 AnySequence 型別中,基本上,將關聯值要求的協議包裝為泛型型別,然后可以直接使用它而無需使使用它的類也是泛型的,現在可以更新之前的 ViewController,使用 AnyModelloader:
class ViewController: UIViewController {
private let modelLoader: AnyModelLoader<MyModel>
init<T: ModelLoading>(modelLoader: T) where T.Model == MyModel {
self.modelLoader = AnyModelLoader(loader: modelLoader)
super.init(nibName: nil, bundle: nil)
}
}
- 至此,我們現在擁有一個面向協議的 API,具有易于 Mock 的特性,且仍然可以在普通類中使用,這歸功于型別擦除,上述技術實際上很好,但它確實涉及額外的步驟,為代碼增加了一些復雜化,但是,事實證明,我們實際上可以直接在視圖控制器中進行基于閉合的型別擦除 ,而不是必須通過 AnyModelloader 類,然后視圖控制器將如下所示:
class ViewController: UIViewController {
private let loadModel: ((Result<MyModel>) -> Void) -> Void
init<T: ModelLoading>(modelLoader: T) where T.Model == MyModel {
loadModel = modelLoader.load
super.init(nibName: nil, bundle: nil)
}
}
- 與型別擦除類 AnyModelloader 一樣,可以參考 load 函式作為閉包的實作,并只需在視圖控制器中保存參考,現在,每當我們想要加載模型時,只需呼叫 loadmodel,就像任何其他函式或閉包一樣:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadModel { result in
switch result {
case .success(let model):
render(model)
case .error(let error):
render(error)
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/336271.html
標籤:其他
上一篇:Koltin實作動態心率曲線
