【问题标题】:Swift: casting un-constrained generic type to generic type that confirms to DecodableSwift:将不受约束的泛型类型转换为确认可解码的泛型类型
【发布时间】:2018-08-16 13:01:43
【问题描述】:

情况

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

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

    // 须藤代码

    ViewModel(APIDataSource (...))

    // 我想在未来改变数据源,比如

    ViewModel(DBDataSource (...))

  • 要从 api ResponseModel 获取数据,需要确认“可解码”,因为我想从 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())

将不胜感激任何帮助或解释这不起作用。在此先感谢:)

【问题讨论】:

  • 最后(对不起,这是最后一条评论,我保证),我怀疑这是stackoverflow.com/questions/33112559/…的另一个案例
  • 嗨,马特,感谢您的快速回复,以下是对您的问题的回复 1. 我想为其他类型的对象使用相同的类并为其用户提供相同的界面,所以我不能使用 Decodable 约束2. 上面的代码过于简单,我实际上想将 JSON 解码为 T 3。是的,这两个问题的原因似乎是相同的,可能有一些解决方法
  • “在获取数据的方法中,我将检查泛型类型的类型,如果它确认“可解码”协议,我将使用它从数据库中的 api 获取数据。”这是一个非常奇怪的语义。您是说如果有人让您的任何数据库模型可解码(可能出于不相关的原因),您是否希望它改变调用者访问网络的行为?如果另一个模块将Decodable 添加到现有类型怎么办?
  • 我强烈建议将有关数据来自何处的信息放入对象中(例如,作为类或实例常量)。然后在运行时检查以决定要做什么。不要试图依赖协议一致性。一旦将泛型添加到组合中,该信息在运行时就不可靠了。
  • 这不清楚:“我将在视图模型中注入两个类中的任何一个,而视图模型将使用该类来获取所需的数据。”模型类是否已经存在并使用加载器来获取数据,或者加载器是否应该生成模型?我相信你是在倒退设计这个系统,所以你的问题最终会问错问题。不过,目前还不清楚调用代码应该是什么样子(使用这个“加载器/模型”系统的东西)。

标签: swift generics casting decode


【解决方案1】:

正如@matt 建议的那样,将我的各种 cmet 转移到“您的问题没有好的解决方案,您需要重新设计您的问题”形式的答案。

你想要做的事情充其量是脆弱的,最坏的情况是不可能的。当您尝试提高性能时,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) 作为参数)。

您的需求可能会有所不同,如果您能更好地描述它们,也许我们会找到更好的解决方案。但它不应该基于某事物是否仅仅符合协议。在大多数情况下这是不可能的,如果你让它工作,它就会很脆弱。

【讨论】:

  • 感谢您的回复,我在那里看到了一些优点。但是,对不起,我的错,我试图简化问题,我错过了一些细节,我再次编辑了我的问题,希望它会更清楚。
【解决方案2】:

你真正想做的是有两个版本的testDecode,一个用于当 T 符合 Decodable 时,另一个用于不符合。因此,您将重载函数testDecode,以便根据 T 的类型调用正确的函数。

不幸的是,你不能这样做,因为你不能做依赖于泛型类型解析的函数重载。但是您可以通过将函数装箱到通用type 中来解决此问题,因为您可以有条件地扩展该类型。

因此,只是为了展示架构:

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,具体取决于解析泛型的类型是否符合协议。

【讨论】:

  • 如果尝试使用它,请记住它有很多锋利的边缘。如果T:P 无法在编译时得到证明,它将回退到默认实现(人们期望它是运行时的,然后会发生意外)。对于提高性能的实现来说,这是一种很棒的技术,但对于改变语义的东西来说,它是非常危险的。
  • @RobNapier 我同意,但我希望你能写出你自己的答案(a)解释我的想法有什么问题,(b)解释 OP 的要求有什么奇怪之处。这两个我都不太会表达!
  • 我总是讨厌以“你的问题没有好的解决方案,你需要重新设计你的问题”的形式来写答案。他们不喜欢答案。 (但仍然,要点。)
  • @RobNapier 您可能会这么说,但您的这种形式的回答对我个人而言极具教育意义!你非常善于解决问题。
  • 这整个问题就像到了圣殿,然后你的绝地大师又请来了另一位绝地大师进行今天的训练。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-22
  • 1970-01-01
相关资源
最近更新 更多