Swift:将不受约束的泛型转换为确认为Decodable的泛型 [英] Swift: casting un-constrained generic type to generic type that confirms to Decodable

查看:98
本文介绍了Swift:将不受约束的泛型转换为确认为Decodable的泛型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

情况

  • 我有两个通用类,它们将从api和数据库中获取数据,比如说APIDataSource< I,O>.和DBDataSource< I,O>.

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

    //sudo代码

    ViewModel(APIDataSource< InputModel,ResponseModel>(...))

    //我想以后更改数据源

    ViewModel(DBDataSource< InputModel,ResponseModel>(...))

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

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

    //sudo代码

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

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

摘录了Playground代码的版本

以下是剥离的代码版本,该代码显示了我想要实现的目标或被困在何处(将不受约束的泛型投射到确认为可解码"的泛型)

  import Foundation//仅用于测试以下功能类DummyModel:可解码{}//剥离函数版本,该函数会将json转换为T类型的对象func解码< T:可解码的>(_类型:T.Type){打印(类型)}//这不会产生编译错误//忽略inpfunc testDecode< T:Decodable>(_ inp:T){解码(T.self)}//这会导致编译错误//忽略inpfunc testDecode2 T(_ inp:T){如果(T.self是可分解的){//??????????//检查T确认为可解码"后,如何在运行时投射T?解码(T.self为!Decodable.Type)}}testDecode(DummyModel()) 

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

解决方案

正如@matt所建议的那样,将我的各种评论以您的问题没有好的解决方案,您需要重新设计问题"的形式移至答案./p>

您尝试做的事情充其量是脆弱的,最坏的情况是不可能的.当您尝试提高性能时,Matt的方法是一个很好的解决方案,但是如果它影响行为,它会以令人惊讶的方式中断.例如:

 协议P {}func doSomething T(x:T)->细绳 {如果x是P {返回"\(x)简单,但实际上是P"}返回"\(x)简单"}func doSomething< T:P>(x:T)->细绳 {返回"\(x)是P"}结构S:P {}doSomething(x:S())//S()是P 

因此,它的工作原理与我们预期的一样.但是我们可以这样丢失类型信息:

  func包装器< T>(x:T)->细绳 {返回doSomething(x:x)}wrapper(x:S())//S()很简单,但实际上是P! 

所以您不能使用泛型来解决这个问题.

回到您的方法,该方法至少有可能变得健壮,但仍然无法正常工作.Swift的类型系统只是无法表达您想说的话.但我认为您还是不应该这样说.

在获取数据的方法中,我将检查通用类型的类型,如果它确认为可解码"协议,则将使用它从数据库中的api else中获取数据.

如果从API与数据库进行的获取代表了不同的语义(而不仅仅是性能的提高),那么即使您能够使它工作,也是非常危险的.程序的任何部分都可以将 Decodable 附加到任何类型.它甚至可以在单独的模块中完成.添加协议一致性绝不能更改程序的语义(外在可见的行为),而只能更改性能或功能.

我有一个通用类,可以从api或数据库中获取数据

完美.如果您已经有一个类,则在这里,类继承很有意义.我可能会像这样构建它:

 类模型{必需的init(identifier:String){}}类DatabaseModel {必需的init(fromDatabaseWithIdentifier:String){}便利init(identifier:String){self.init(fromDatabaseWithIdentifier:identifier)}}类APIModel {必需的init(fromAPIWithIdentifier:String){}便利init(identifier:String){self.init(fromAPIWithIdentifier:identifier)}}类SomeModel:DatabaseModel {必需的init(fromDatabaseWithIdentifier标识符:字符串){super.init(fromDatabaseWithIdentifier:标识符)}} 

根据您的确切需求,您可以重新安排此操作(并且此处的协议也可能可行).但是关键是模型知道如何获取自身.这样可以轻松在类内部使用Decodable(因为它可以轻松地使用 type(of:self)作为参数).

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

Situation

  • I have a two generic classes which will fetch data either from api and database, lets say APIDataSource<I, O> and DBDataSource<I, O> respectively

  • I will inject any of two class in view-model when creating it and view-model will use that class to fetch data it needed. I want view-model to work exactly same with both class. So I don't want different generic constraints for the classes

    // sudo code

    ViewModel(APIDataSource <InputModel, ResponseModel>(...))

    // I want to change the datasource in future like

    ViewModel(DBDataSource <InputModel, ResponseModel>(...))

  • To fetch data from api ResponseModel need to confirms to "Decodable" because I want to create that object from JSON. To fetch data from realm database it need to inherit from Object

  • Inside ViewModel I want to get response like

    // sudo code

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

  • If developer tries to fetch api data from database or vice-versa it will check for correct type and throws proper error.

Stripped out version of code for playground

Following is stripped out version of code which shows what I want to achieve or where I am stuck (casting un-constrained generic type to generic type that confirms to 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())

Any help or explanation that this could not work would be appreciated. Thanks in advance :)

解决方案

As @matt suggests, moving my various comments over to an answer in the form "your problem has no good solution and you need to redesign your problem."

What you're trying to do is at best fragile, and at worst impossible. Matt's approach is a good solution when you're trying to improve performance, but it breaks in surprising ways if it impacts behavior. For example:

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

So that works just like we expect. But we can lose the type information this way:

func wrapper<T>(x: T) -> String {
    return doSomething(x: x)
}

wrapper(x: S())  // S() simple, but it's really P!

So you can't solve this with generics.

Going back to your approach, which at least has the possibility of being robust, it's still not going to work. Swift's type system just doesn't have a way to express what you're trying to say. But I don't think you should be trying to say this anyway.

In the method that fetch data I will check type of generic type and if it confirms to "Decodable" protocol I will use it to fetch data from api else from database.

If fetching from the API vs the database represents different semantics (rather than just a performance improvement), this is very dangerous even if you could get it to work. Any part of the program can attach Decodable to any type. It can even be done in a separate module. Adding protocol conformance should never change the semantics (outwardly visible behaviors) of the program, only the performance or capabilities.

I have a generic class which will fetch data either from api or database

Perfect. If you already have a class, class inheritance makes a lot of sense here. I might build it like:

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

Depending on your exact needs, you might rearrange this (and a protocol might also be workable here). But the key point is that the model knows how to fetch itself. That makes it easy to use Decodable inside the class (since it can easily use type(of: self) as the parameter).

Your needs may be different, and if you'll describe them a bit better maybe we'll come to a better solution. But it should not be based on whether something merely conforms to a protocol. In most cases that will be impossible, and if you get it working it will be fragile.

这篇关于Swift:将不受约束的泛型转换为确认为Decodable的泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆