Prine Prine - 6 months ago 17
Swift Question

Generics call with Type T in Swift

In my application I want to create an generic method which creates an array of object depening on the given type T.

I created the following function:

func getArray<T : ROJSONObject>(key:String) -> T[] {
var elements = T[]()

for jsonValue in getValue(key).array! {
var element = T()

element.jsonData = jsonValue
elements.append(element)
}

return elements
}


Now I want to pass the type when I call the method, so it does know which type it should create internally. I think in Java and C# you can use a method like that:

object.getArray<Document>("key")


When I call it like that, I always get the error:

Cannot explicitly specialize a generic function


So my fix was to define an additional parameter containing an instance of the type T so it does automatically detect the type:

func getArray<T : ROJSONObject>(key:String, type:T) -> T[] {
var elements = T[]()

for jsonValue in getValue(key).array! {
var element = T()

element.jsonData = jsonValue
elements.append(element)
}

return elements
}


Is there really no other way to get that behaviour without passing an unused instance? Or am I misunterstanding something?

Further Testing

After the answer of jtbandes I did some more testing. I tried to force the Type by adding the
as
in the call.

class Person {

init() { }

func getWorkingHours() -> Float {
return 40.0
}
}

class Boss : Person {
override func getWorkingHours() -> Float {
println(100.0)
return 100.0
}
}

class Worker : Person {
override func getWorkingHours() -> Float {
println(42.0)
return 42.0
}
}

func getWorkingHours<T : Person>() -> T {
var person = T()
person.getWorkingHours()

return person
}

var worker:Worker = getWorkingHours() as Worker
var boss:Boss = getWorkingHours() as Boss
worker.getWorkingHours() // prints out 40.0 instead of 42.0
boss.getWorkingHours() // prints out 40.0 instead of 100.0


So somehow the type is always the base type even I've specified the type with the
as
keyword. I know the example does not make much sense, but it was just for testing purpose..

Answer

I think it is a bug.

You can workaround it by make base class inherited from NSObject or mark constructor of base class with @required

import Cocoa

class A : NSObject {
    init() { }
}
class B : A {}
class C : A {}

func Create<T:NSObject> () -> T {
    return T()
}

println(Create() as A)
println(Create() as B)
println(Create() as C)

//<_TtC11lldb_expr_01A: 0x7f85ab717bc0>
//<_TtC11lldb_expr_01B: 0x7f85ab451e00>
//<_TtC11lldb_expr_01C: 0x7f85ab509160>

class D {
    @required init() { } 
}

class E : D {
    init() { }
}

class F : D {
    init() { }
}

func Create2<T:D> () -> T {
    return T()
}

println(Create2() as D)
println(Create2() as E)
println(Create2() as F)

//C11lldb_expr_01D (has 0 children)
//C11lldb_expr_01E (has 1 child)
//C11lldb_expr_01F (has 1 child)

Not sure why @required solve the problem. But this is the reference

required

Apply this attribute to a designated or convenience initializer of a class to indicate that every subclass must implement that initializer.

Required designated initializers must be implemented explicitly. Required convenience initializers can be either implemented explicitly or inherited when the subclass directly implements all of the superclass’s designated initializers (or when the subclass overrides the designated initializers with convenience initializers).