Vous l'abordez de la mauvaise manière: dans Swift, contrairement à Objective-C, les classes ont des types spécifiques et ont même une hiérarchie d'héritage (c'est-à-dire que si la classe B
hérite de A
, alors B.Type
hérite également de 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
C'est pourquoi vous ne devriez pas utiliser AnyClass
, sauf si vous voulez vraiment autoriser n'importe quelle classe. Dans ce cas, le bon type serait T.Type
, car il exprime le lien entre le returningClass
paramètre et le paramètre de la fermeture.
En fait, l'utiliser à la place de AnyClass
permet au compilateur de déduire correctement les types dans l'appel de méthode:
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())
}
Maintenant, il y a le problème de la construction d'une instance de T
à passer handler
: si vous essayez d'exécuter le code maintenant, le compilateur se plaindra que ce T
n'est pas constructible avec ()
. Et à juste titre: T
doit être explicitement contraint d'exiger qu'il implémente un initialiseur spécifique.
Cela peut être fait avec un protocole comme le suivant:
protocol Initable {
init()
}
class CityInfo : NSObject, Initable {
var cityName: String?
var regionCode: String?
var regionName: String?
// Nothing to change here, CityInfo already implements init()
}
Ensuite, il vous suffit de modifier les contraintes génériques invokeService
de <T>
à <T: Initable>
.
Pointe
Si vous obtenez des erreurs étranges comme "Impossible de convertir le type de l'expression '()' en type 'Chaîne'", il est souvent utile de déplacer chaque argument de l'appel de méthode vers sa propre variable. Cela permet de réduire le code à l'origine de l'erreur et de découvrir les problèmes d'inférence de type:
let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self
CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/
}
Maintenant, il y a deux possibilités: l'erreur se déplace vers l'une des variables (ce qui signifie que la mauvaise partie est là) ou vous obtenez un message cryptique comme "Impossible de convertir le type de l'expression ()
en type ($T6) -> ($T6) -> $T5
".
La cause de cette dernière erreur est que le compilateur n'est pas en mesure de déduire les types de ce que vous avez écrit. Dans ce cas, le problème est qu'il T
n'est utilisé que dans le paramètre de la fermeture et que la fermeture que vous avez passée n'indique aucun type particulier, de sorte que le compilateur ne sait pas quel type déduire. En modifiant le type de returningClass
pour inclure, T
vous donnez au compilateur un moyen de déterminer le paramètre générique.
CastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }