How to pass a class type as a function parameter

Jean-Francois Gagnon picture Jean-Francois Gagnon · Jun 19, 2014 · Viewed 97k times · Source

I have a generic function that calls a web service and serialize the JSON response back to an object.

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

What I'm trying to accomplish is is the equivalent of this Java code

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Is my method signature for what I'm trying to accomplish correct?
  • More specifically, is specifying AnyClass as a parameter type the right thing to do?
  • When calling the method, I'm passing MyObject.self as the returningClass value, but I get a compilation error "Cannot convert the expression's type '()' to type 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Edit:

I tried using object_getClass, as mentioned by holex, but now I get:

error: "Type 'CityInfo.Type' does not conform to protocol 'AnyObject'"

What need to be done to conform to the protocol?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}

Answer

EliaCereda picture EliaCereda · Jul 14, 2014

You are approaching it in the wrong way: in Swift, unlike Objective-C, classes have specific types and even have an inheritance hierarchy (that is, if class B inherits from A, then B.Type also inherits from A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

That's why you shouldn't use AnyClass, unless you really want to allow any class. In this case the right type would be T.Type, because it expresses the link between the returningClass parameter and the parameter of the closure.

In fact, using it instead of AnyClass allows the compiler to correctly infer the types in the method call:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

Now there's the problem of constructing an instance of T to pass to handler: if you try and run the code right now the compiler will complain that T is not constructible with (). And rightfully so: T has to be explicitly constrained to require that it implements a specific initializer.

This can be done with a protocol like the following one:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

Then you only have to change the generic constraints of invokeService from <T> to <T: Initable>.

Tip

If you get strange errors like "Cannot convert the expression's type '()' to type 'String'", it is often useful to move every argument of the method call to its own variable. It helps narrowing down the code that is causing the error and uncovering type inference issues:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Now there are two possibilities: the error moves to one of the variables (which means that the wrong part is there) or you get a cryptic message like "Cannot convert the expression's type () to type ($T6) -> ($T6) -> $T5".

The cause of the latter error is that the compiler is not able to infer the types of what you wrote. In this case the problem is that T is only used in the parameter of the closure and the closure you passed doesn't indicate any particular type so the compiler doesn't know what type to infer. By changing the type of returningClass to include T you give the compiler a way to determine the generic parameter.