web-dev-qa-db-fra.com

Comment passer un type de classe en tant que paramètre de fonction

J'ai une fonction générique qui appelle un service Web et sérialise la réponse JSON en un objet.

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 */
}

Ce que j'essaie d'accomplir est l'équivalent de ce code Java

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Est-ce que la signature de ma méthode pour ce que j'essaie d'accomplir est correcte?
  • Plus spécifiquement, la spécification de AnyClass en tant que type de paramètre est-elle la bonne chose à faire?
  • Lors de l'appel de la méthode, je passe MyObject.self comme valeur de returnClass, mais la compilation erreur "Impossible de convertir le type de l'expression '()' en type 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Edit:

J'ai essayé d'utiliser object_getClass, comme mentionné par holex, mais maintenant je reçois:

erreur: "Le type 'CityInfo.Type' n'est pas conforme au protocole 'AnyObject'"

Que faut-il faire pour se conformer au protocole?

class CityInfo : NSObject {

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

Vous l’approchez de la mauvaise façon: dans Swift, contrairement à Objective-C, les classes ont des types spécifiques et 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 la classe toute. Dans ce cas, le bon type serait T.Type, car il exprime le lien entre le paramètre returningClass et le paramètre de la fermeture.

En fait, son utilisation à 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())
}

Il y a maintenant le problème de la construction d'une instance de T à transmettre à handler: si vous essayez d'exécuter le code maintenant, le compilateur se plaindra que T n'est pas constructible avec (). Et à juste titre: T doit être explicitement contraint de nécessiter l'implémentation d'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 de invokeService de <T> à <T: Initable>.

Pointe

Si vous obtenez des erreurs étranges telles que "Impossible de convertir le type de l'expression '()' en type 'String'", il est souvent utile de déplacer chaque argument de l'appel de méthode vers sa propre variable. Cela aide à circonscrire le code qui cause l'erreur et à 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 présente) ou vous obtenez un message cryptique du type "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 que 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. Le compilateur ne sait donc pas quel type inférer. En changeant le type de returningClass pour inclure T, vous donnez au compilateur un moyen de déterminer le paramètre générique.

131
EliaCereda

vous pouvez obtenir la classe de AnyObject de cette manière:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

et vous pourrez le passer en tant que paramère plus tard, si vous le souhaitez.

32
holex

J'ai un cas d'utilisation similaire dans Swift5:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

2
johney

Utilisez obj-getclass :

_CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}
_

En supposant que soi-même soit un objet d'information de ville.

0
Undo