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