簡體   English   中英

Swift:將不受約束的泛型轉換為確認為Decodable的泛型

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM