web-dev-qa-db-fra.com

Appel générique de type T en Swift

Dans mon application, je souhaite créer une méthode générique qui crée un tableau d'objets dépendant du type donné T.

J'ai créé la fonction suivante:

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
}

Maintenant, je veux passer le type quand j'appelle la méthode, afin qu'il sache quel type il doit créer en interne. Je pense qu'en Java et en C #, vous pouvez utiliser une méthode comme celle-ci:

object.getArray<Document>("key")

Quand je l'appelle comme ça, j'ai toujours l'erreur:

Cannot explicitly specialize a generic function

Ma solution consistait donc à définir un paramètre supplémentaire contenant une instance du type T afin de détecter automatiquement le 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
}

N'y a-t-il vraiment aucun autre moyen d'obtenir ce comportement sans passer une instance inutilisée? Ou est-ce que je comprends mal quelque chose?

D'autres tests

Après la réponse de jtbandes, j'ai fait d'autres tests. J'ai essayé de forcer le type en ajoutant le as à l'appel.

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

Ainsi, le type est toujours le type de base même si j'ai spécifié le type avec le mot clé as. Je sais que cet exemple n’a pas beaucoup de sens, mais c’était juste pour le test.

16
Prine

Je pense que c'est un bug. 

Vous pouvez contourner ce problème en faisant de la classe une sous-classe de NSObject ou un constructeur de marque de la classe de base avec @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)

Vous ne savez pas pourquoi @required résout le problème. Mais c'est la référence

champs obligatoires

Appliquez cet attribut à un initialiseur désigné ou pratique D'une classe pour indiquer que chaque sous-classe doit implémenter cet initialiseur .

Les initialiseurs désignés requis doivent être implémentés explicitement. Les initialiseurs de commodité requis peuvent être implémentés explicitement Ou hérités lorsque la sous-classe implémente directement tous les initialiseurs désignés par la [. les initialiseurs désignés avec des initialiseurs pratiques).

4
Bryan Chen

J'ai résolu ce problème en empruntant la fonction d'exécution Swift unsafeBitCast.

Il est déclaré en tant que func unsafeBitCast<T, U>(x: T, _: U.Type) -> U et vous pouvez l'appeler en tant que unsafeBitCast(value, MyType).

Appliqué à votre fonction, ce serait

func getArray<T : ROJSONObject>(key:String, _: T.Type) -> T[] {
    // function stays the same
}

Et vous pouvez l'appeler comme ça

getArray("key", Document)
14
Alfonso

Vous devriez pouvoir indiquer le type avec le mot clé as: object.getArray("key") as Document[].

6
jtbandes

Vous devez créer un initialiseur required, puis ajouter la ligne let realType = T.self et remplacer T() par realType().

class Person {
    required 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 {
    let realType = T.self
    var person = realType()
    person.getWorkingHours()

    return person
}
2
mixel

Version de travail du code dans la question (deuxième partie)

• Init est requis car une fonction renvoie un type de personne générique ( newPerson(as person:T) -> T).

• J'ai remplacé les méthodes de classe func getWorkingHours() par var workingHours { get } .. Cela fonctionne également, de manière plus «rapide».

getWorkingHours() renvoie les heures, comme son nom l'indique, pas une personne. 

class Person {
    required init() {}
    var workingHours: Float { get { return 40 } }
}

class Boss : Person {
    override var workingHours: Float { get { return 100 } }
}

class Worker : Person {
    override var workingHours: Float { get { return 42 } }
}

func getWorkingHours<T : Person>(_ person: T) -> Float {
    return person.workingHours
}

func newPerson<T : Person>(as person: T) -> T {
    return T()
}

// -----------------------------------------------

let worker = Worker()
let boss = Boss()

print( getWorkingHours(worker) ) // prints 42.0
print( getWorkingHours(boss) ) // prints 100.0

let person = newPersonOfSameClass(as: worker)

print( getWorkingHours(person) ) // prints 42.0

J'espère que ça aide .. Ce code est prêt à être copié/collé dans la cour de récréation.

0
Moose

Ne l'appelle pas comme

object.getArray<Document>("key")

Au lieu de cela, laissez de côté la partie "<Document>", comme ceci:

object.getArray("key")

Correction du problème pour moi.

0
Lukas Kalinski

Pour répondre à la première partie de votre question

Il n'est pas nécessaire d'ajouter un paramètre supplémentaire à la méthode pour spécifier le type de T puisque vous battez déjà [T] en tant que type de retour 

alors vous remplacez cette ligne 

object.getArray<Document>("key")

avec ça

object.getArray("key") as [Document]

De cette façon, vous aurez l’occasion de spécifier le type à T

0
thesummersign