8

I have a problem with generics in Swift (3):

I get different data of different classes, implementing the same protocol, from server and I need to put them into a class with generics (e.g. Array).

I do not know which class the data will be so I need to use the protocol. So I have following structure:

My protocol:

protocol MyProtocol {
    // some protocol stuff
}

Some classes implementing the protocol

class MyProtocolImpl1: MyProtocol{
    // some class stuff
}

class MyProtocolImpl2: MyProtocol {
    // some class stuff
}
....

class with generic:

final class MyGenericsClass<T: MyProtocol> {
    // some class stuff
}

now I want to use this class this way:

func createClass<T>(model: T.Type) -> MyGenericClass<T> {
    let myClass = MyGenericClass<T>()
    return myClass
}

...

EDIT

func getClass() -> MyProtocol.Type {
     return MyProtocolImpl1.self
}

let impl1 = getClass()
let impl2 = MyProtocolImpl2.self

let createdClass = createClass(impl1) //not working
let createdClass = createClass(impl2) //working

doing createClass(impl1) I get this error:

cannot invoke 'createClass' with an argument list of type '(MyProtocol.Type)'

Changing the MyProtocol to a class would fix the problem but then I could not be sure every class inheriting from it implements the needed methods.

Does someone have some ideas how to solve this problem?

beal
  • 435
  • 4
  • 15
  • 1
    Does `func createClass` fix it? – BallpointBen Jan 05 '17 at 15:39
  • were you able to resolve the issue? – Bista Jan 05 '17 at 16:20
  • 1
    Well you want to pass `MyProtocol.self` into the function, not `MyProtocol.Type`. But even then, you cannot have a `MyGenericClass` as `MyProtocol` isn't a type that conforms to `MyProtocol` – see [Protocol doesn't conform to itself?](http://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself) – Hamish Jan 05 '17 at 16:33
  • the explanations in this link are very helpful. Thanks for this. So if Swift does not support this right at the moment, is there some nice workaround for the problem? Do you have some ideas? – beal Jan 06 '17 at 08:46
  • @beal Really depends on what you're using `MyGenericClass` for. The real crux of the problem is that you're trying to mix generic placeholders, which are resolved at compile time with types that you're obtaining at runtime. Most likely you should be using a non-generic class and use the type `MyProtocol` instead of the generic placeholder `T`. Now you're talking about *any* type that conforms to `MyProtocol` rather than one specific type – as you *don't know* what that type is until runtime. – Hamish Jan 06 '17 at 14:27

2 Answers2

2

The answer is that you can't do this. MyProtocol.self is not a concrete type, ergo it cannot conform to MyProtocol. You cannot pass MyProtocol when your function expects a concrete type T, following T: MyProtocol.

Think about it this way; if I were to add a static function, doFoo() to MyProtocol, and you initialized GenericClass<MyProtocol> and decided to call MyProtocol.doFoo() inside some method, what function would it call? It doesn't make any sense, because MyProtocol is not a concrete type (i.e. a class, struct or enum), it's a protocol.


A note on your comment:

The problem is that I do not know if it is ClassA or ClassB or ClassX. I just know that it is a subclass of MyProtocol

This is where you are erring. "subclass of MyProtocol" does not make sense, because MyProtocol is not a class.


How about this? You end up with AnyObject, but you can cast it to MyGenericClass<T> later when you do know T.

extension MyProtocol
{
    static func createMyGenericClass() -> AnyObject
    {
        return MyGenericClass<Self>()
    }
}

func createClass(_ modelType: MyProtocol.Type) -> AnyObject
{
    return modelType.createMyGenericClass()
}

You could also make MyGenericClass inherit from some base class, and have that store a variable for MyProtocol.Type. That way, you could use specific functions from your MyProtocol conformee type.

Vatsal Manot
  • 17,695
  • 9
  • 44
  • 80
  • Thanks for the answer. But this isn't solving my problem. I edited the question to make it clearer. I want to pass an implementation of MyProtocol to `createClass()` e. g. `MyProtocolImpl.self` – beal Jan 06 '17 at 09:12
  • Interesting solutions but they are not working for me: In your first solution, I'm not able to cast `Any` because it is only known at runtime. Your second solution will not work in my case because `MyGenericClass` comes from a framework – beal Jan 06 '17 at 09:45
  • @user7302727: All Swift classes are internally Objective-C classes. You can use associated objects to store your `MyProtocol` conformee's type (ex. `ClassA.self`, `ClassB.self`) in an instance of `MyGenericClass`. – Vatsal Manot Jan 06 '17 at 09:48
  • But to instanciate MyGenericClass (for setting the associated Object), I need to give it a specific class. – beal Jan 06 '17 at 11:54
  • @user7302727: No, you don't. Simply use my first solution, and cast it to an `AnyObject`. You don't even need to cast, a simple modification to my original code solves the problem. – Vatsal Manot Jan 06 '17 at 11:55
0

I did not exactly understand what you are trying to do, but I thing this is what you want.

import Foundation


protocol MyProtocol {
}

class ClassA : MyProtocol {
}

class ClassB : MyProtocol {
}

final class GenericClass<T:MyProtocol> {

}

func createClass<T:MyProtocol>(_ model: T.Type) -> GenericClass<T> {
    let myClass = GenericClass<T>()
    return myClass
}
let createdClass = createClass(ClassA.self)
BangOperator
  • 4,377
  • 2
  • 24
  • 38
  • The problem is that I do not know if it is ClassA or ClassB or ClassX. I just know that it is a subclass of MyProtocol – beal Jan 05 '17 at 17:10