Swift:将不受约束的泛型类型转换为确认可解码的泛型类型

2023-12-27

情况

  • 我有两个通用类,它们将从 api 和数据库获取数据,分别说 APIDataSource 和 DBDataSource

  • 创建视图模型时,我将在视图模型中注入两个类中的任何一个,视图模型将使用该类来获取所需的数据。我希望视图模型与这两个类完全相同。所以我不希望这些类有不同的通用约束

    // 须藤代码

    ViewModel(APIDataSource (...))

    // 我想将来更改数据源,例如

    ViewModel(DBDataSource (...))

  • 要从 api 响应模型获取数据,需要确认“可解码”,因为我想从 JSON 创建该对象。要从领域数据库获取数据,需要从 Object 继承

  • 在 ViewModel 内部我想得到像这样的响应

    // 须藤代码

    self.dataSource.request("param1", "param2")

  • 如果开发人员尝试从数据库获取 api 数据,反之亦然,它将检查类型是否正确并抛出正确的错误。

游乐场代码的删除版本

以下是代码的删除版本,它显示了我想要实现的目标或我陷入困境的地方(将不受约束的泛型类型转换为确认可解码的泛型类型)

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 所建议的,将我的各种评论转移到“你的问题没有好的解决方案,你需要重新设计你的问题”的形式的答案。

你想要做的事情往好里说是脆弱的,往坏了说是不可能的。当您尝试提高性能时,马特的方法是一个很好的解决方案,但如果它影响行为,它就会以令人惊讶的方式出现问题。例如:

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)
    }
}

根据您的具体需求,您可以重新安排(并且协议也可能在这里可行)。但关键的一点是model知道如何获取自身。这使得在类中使用 Decodable 变得很容易(因为它可以轻松使用type(of: self)作为参数)。

您的需求可能不同,如果您能更好地描述它们,也许我们会找到更好的解决方案。但它不应该基于某些东西是否仅仅符合协议。在大多数情况下这是不可能的,而且即使你让它发挥作用,它也会很脆弱。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Swift:将不受约束的泛型类型转换为确认可解码的泛型类型 的相关文章