[英]Swift: casting un-constrained generic type to generic type that confirms to Decodable
情況
我有兩個通用類,它們將從api和數據庫中獲取數據,分別說APIDataSource <I,O>和DBDataSource <I,O>
在創建模型時,我將在視圖模型中注入兩個類中的任何一個,並且視圖模型將使用該類來獲取所需的數據。 我希望視圖模型與兩個類完全相同。 所以我不想對類使用不同的通用約束
// sudo代碼
ViewModel(APIDataSource <InputModel,ResponseModel>(...))
//我想將來更改數據源,例如
ViewModel(DBDataSource <InputModel,ResponseModel>(...))
要從api ResponseModel獲取數據,需要確認為“ Decodable”,因為我想從JSON創建該對象。 要從領域數據庫獲取數據,需要從Object繼承
在ViewModel里面我想得到像
// sudo代碼
self.dataSource.request(“ param1”,“ param2”)
如果開發人員嘗試從數據庫中獲取api數據,反之亦然,它將檢查類型是否正確並拋出正確的錯誤。
去除了操場的代碼版本
以下是摘錄的代碼版本,該代碼顯示了我想要實現的目標或遇到的問題(將不受約束的泛型投射到確認為Decodable的泛型)
import Foundation
// Just to test functions below
class DummyModel: Decodable {
}
// Stripped out version of function which will convert json to object of type T
func decode<T:Decodable>(_ type: T.Type){
print(type)
}
// This doesn't give compilation error
// Ignore the inp
func testDecode<T:Decodable> (_ inp: T) {
decode(T.self)
}
// This gives compilation error
// Ignore the inp
func testDecode2<T>(_ inp: T){
if(T.self is Decodable){
// ??????????
// How can we cast T at runtime after checking T confirms to Decodable??
decode(T.self as! Decodable.Type)
}
}
testDecode(DummyModel())
任何無法解決的幫助或解釋將不勝感激。 提前致謝 :)
正如@matt建議的那樣,將我的各種評論以“您的問題沒有好的解決方案,您需要重新設計問題”的形式移至答案。
您嘗試做的事情充其量是脆弱的,最壞的情況是不可能的。 當您嘗試提高性能時,Matt的方法是一個很好的解決方案,但是如果它影響行為,它會以令人驚訝的方式中斷。 例如:
protocol P {}
func doSomething<T>(x: T) -> String {
if x is P {
return "\(x) simple, but it's really P"
}
return "\(x) simple"
}
func doSomething<T: P>(x: T) -> String {
return "\(x) is P"
}
struct S: P {}
doSomething(x: S()) // S() is P
這樣就可以像我們期望的那樣工作。 但是我們可以這樣丟失類型信息:
func wrapper<T>(x: T) -> String {
return doSomething(x: x)
}
wrapper(x: S()) // S() simple, but it's really P!
因此,您無法使用泛型解決此問題。
回到您的方法,該方法至少有可能變得很健壯,但仍然行不通。 Swift的類型系統只是無法表達您想說的話。 但是我不認為您應該這樣說。
在獲取數據的方法中,我將檢查通用類型的類型,如果它確認為“可解碼”協議,我將使用它從數據庫中的其他API中獲取數據。
如果從API與數據庫進行的獲取表示不同的語義(而不僅僅是性能改進),那么即使您可以使它工作也非常危險。 程序的任何部分都可以將Decodable
附加到任何類型。 它甚至可以在單獨的模塊中完成。 添加協議一致性永遠不要更改程序的語義(外在可見的行為),而只能更改性能或功能。
我有一個通用類,它將從api或數據庫中獲取數據
完善。 如果您已經有一個類,則在這里類繼承很有用。 我可能會像這樣構建它:
class Model {
required init(identifier: String) {}
}
class DatabaseModel {
required init(fromDatabaseWithIdentifier: String) {}
convenience init(identifier: String) { self.init(fromDatabaseWithIdentifier: identifier )}
}
class APIModel {
required init(fromAPIWithIdentifier: String) {}
convenience init(identifier: String) { self.init(fromAPIWithIdentifier: identifier )}
}
class SomeModel: DatabaseModel {
required init(fromDatabaseWithIdentifier identifier: String) {
super.init(fromDatabaseWithIdentifier: identifier)
}
}
根據您的確切需求,您可以重新安排它(並且此處的協議也可能可行)。 但是關鍵是模型知道如何獲取自身。 這使得在類內部易於使用Decodable(因為它可以輕松地使用type(of: self)
作為參數)。
您的需求可能有所不同,如果您能更好地描述它們,也許我們會提供更好的解決方案。 但這不應基於某些事物是否僅符合協議。 在大多數情況下,這是不可能的,並且如果您能夠正常運行,它將非常脆弱。
您在這里真正想做的是有兩個版本的testDecode
,一個用於T符合Decodable的版本,另一個用於T不符合Decodable的版本。 因此,您將重載函數testDecode
以便根據T的類型調用正確的函數。
不幸的是,您無法執行此操作,因為您無法執行依賴於泛型類型的解析的函數重載。 但是您可以通過將函數裝在通用類型中來解決此問題,因為您可以有條件地擴展類型。
因此,僅顯示架構:
protocol P{}
struct Box<T> {
func f() {
print("it doesn't conform to P")
}
}
extension Box where T : P {
func f() {
print("it conforms to P")
}
}
struct S1:P {}
struct S2 {}
let b1 = Box<S1>()
b1.f() // "it conforms to P"
let b2 = Box<S2>()
b2.f() // "it doesn't conform to P"
這證明根據解析泛型的類型是否符合協議,正在調用正確的f
版本。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.