Swift:将不受约束的通用类型转换为可解码的通用类型

时间:2018-08-16 13:01:44

标签: swift generics casting decode

情况

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

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

    // sudo代码

    ViewModel(APIDataSource (...))

    //我想以后更改数据源

    ViewModel(DBDataSource (...))

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

  • 在ViewModel内部,我想得到类似的响应

    // sudo代码

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

任何无法解决的帮助或解释将不胜感激。在此先感谢:)

2 个答案:

答案 0 :(得分:2)

正如@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 else获取数据。

如果从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)作为参数)。

您的需求可能有所不同,如果您将它们描述得更好,也许我们会提供更好的解决方案。但这不应基于某些事物是否仅符合协议。在大多数情况下,这是不可能的,并且如果您能够正常运行,它将非常脆弱。

答案 1 :(得分:1)

您真正想做的是有两个版本的testDecode,一个用于T符合Decodable时,另一个用于不符合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,这取决于解析泛型的类型是否符合协议。