Marco
Marco

Reputation: 89

Generic function to check if generic class received is of a generic type

I know the title is confusing but I could not find a better way to condense this: I have a generic function that needs to check if the generic class it receives belongs to a generic type of class or to another type of class, without knowing the final parameter of the generic class. This is a sample of what I am trying to solve:

public class GenericClass<T: Codable>: SomeProtocol {

    var data: T?

    init(data: T) {
        self.data = data
    }
}

public class LessGenericClass: SomeProtocol {

    var specific: String?

    init(specific: String) {
        self.specific = specific
    }
}


public func foo<T: SomeProtocol>(caller: T)    {

    if (caller as? GenericClass) != nil { /* do something */  }  //   --->>> THIS IS THE CRITICAL PART
    if (caller as? LessGenericClass) != nil   { /* do something else */  }
}

The issue I have is that the compiler expects that in the function foo the GenericClass is resolved. In the code above the error would be: 'Generic parameter 'T' could not be inferred in cast to 'GenericClass''. For example, this would work:

public func foo<T: SomeProtocol>(caller: T, completion: @escaping(T.returnType?) -> () )    {

    if (caller as? GenericClass<String>) != nil { /* do something */  }  //  
    if (caller as? LessGenericClass) != nil   { /* do something else */  }
}

But if I have to declare every single type I can pass to the foo function it would defy the purpose of using generics. Any help would be appreciated

Upvotes: 0

Views: 378

Answers (2)

Cristik
Cristik

Reputation: 32775

Even if you'd be able to do this, check if the passed argument is part of a possibly infinite set of generic types, how would you check the generic argument?

If you don't care about the generic argument, and you want to only find out if the argument is a GenericClass, you could do some stringly programing:

if "\(T.self)".hasPrefix("GenericClass<") { /* do your critical stuff */ }

Note that this is error prone, as if you rename the class you'll also have to keep the strings updated.

Upvotes: 0

vadian
vadian

Reputation: 285039

This kind of runtime check is very unswifty.

A swiftier way is to add doSomething as a protocol requirement

protocol SomeProtocol {
    func doSomething()
    // other stuff
}

public func foo<T: SomeProtocol>(caller: T, completion: @escaping(T.returnType?) -> ()) {

    caller.doSomething()
}

Upvotes: 1

Related Questions