2

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

Dev Khadka
  • 5,142
  • 4
  • 19
  • 33
  • Finally (sorry, this the last comment, I promise), I suspect that under the hood this is another case of https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself – matt Aug 16 '18 at 13:19
  • Hi Matt, thanks for quick response, following are response to your questions 1. I want to use same class for other types of object and give same interface for its user so I can't use Decodable constrain 2. Above code is over-simplified, I actually want to decode JSON to T 3. Ya, it seems cause of both issues are same, may be there is some work around – Dev Khadka Aug 16 '18 at 15:08
  • 1
    "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." This is a very strange semantic. Are you saying that if anyone makes any of your database models Decodable (perhaps for unrelated reasons), you want that to change the behavior of callers to access the network? What if another module adds `Decodable` to an existing type? – Rob Napier Aug 16 '18 at 15:26
  • I would strongly recommend putting the information about where the data comes from into the object (as a class or instance constant for instance). Then check that at runtime to decide what to do. Don't try to rely on protocol conformance. That information is not reliable at runtime once you add generics into the mix. – Rob Napier Aug 16 '18 at 15:30
  • This is unclear: "I will inject any of two class in view-model and view-model will use that class to fetch data it needed." Does the model class exist already and use the loader to fetch data, or is the loader expected to generate models? I believe you're designing this system backwards, so your question winds up asking the wrong thing. It's not clear, though, what the calling code is expected to look like (the thing that uses this "loader/model" system). – Rob Napier Aug 16 '18 at 19:58
  • @RobNapier By inject I mean, when instantiating view model I will send instance of a datasource class for viewmodel to use The model class already exists and datasource class will load the model from either api or datasource may be my approach is wrong, but my aim is, view model remains unchanged even if datasource changed. I have edited question to give information you asked – Dev Khadka Aug 18 '18 at 09:56

2 Answers2

2

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks for reply, I saw some good points there. But, Sorry, it my bad, I was trying to simplify the question and I missed some detail, I edited my question again, hope it will make more clear. – Dev Khadka Aug 16 '18 at 17:45
1

What you'd really like to do here is have two versions of testDecode, one for when T conforms to Decodable, the other for when it doesn't. You would thus overload the function testDecode so that the right one is called depending on the type of T.

Unfortunately, you can't do that, because you can't do a function overload that depends on the resolution of a generic type. But you can work around this by boxing the function inside a generic type, because you can extend the type conditionally.

Thus, just to show the architecture:

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"

This proves that the right version of f is being called, depending on whether the type that resolves the generic conforms to the protocol or not.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Keep in mind if trying to use this that it has a lot of sharp edges. If `T:P` can't be proven at compile time, it'll fall back to the default implementation (folks expect it to be runtime, and then the surprises happen). This is a great technique for implementations that improve performance, but it is very dangerous for things that change semantics. – Rob Napier Aug 16 '18 at 15:28
  • @RobNapier I agree but I wish you'd write your own answer (a) explaining what's wrong with my idea and (b) explaining what's weird about the OP's requirement. I wasn't quite able to express either of those! – matt Aug 16 '18 at 15:30
  • I always hate writing answers in the form "your problem has no good solution and you need to redesign your problem." They don't feel like answers. (But still, point taken.) – Rob Napier Aug 16 '18 at 15:31
  • @RobNapier You may say that, but your answers of that form have been extremely educational to me personally! You're very good at seeing around the problem. – matt Aug 16 '18 at 15:43
  • This whole Question is like arriving at the temple and then your Jedi Master has invited another Jedi master for today's training. – Bart van Kuik Aug 17 '18 at 05:38