Introspection et génériques de classe Swift


121

J'essaye de créer dynamiquement un class type basé sur instance en utilisant des génériques, mais je rencontre des difficultés avec l'introspection de classe.

Voici les questions:

  • Y a-t-il un équivalent Swift aux Obj-C self.class ?
  • Existe-t-il un moyen d'instancier une classe en utilisant le AnyClassrésultat deNSClassFromString ?
  • Existe-t-il un moyen d'obtenir AnyClassou de taper des informations strictement à partir d'un paramètre générique T? (Similaire à la typeof(T)syntaxe de C # )

2
stackoverflow.com/a/24069875/292145 donne quelques conseils sur l'API de réflexion Swift.
Klaas

5
Objective-C self.classdeviendrait self.dynamicType.selfdans la croyance Swift I
Filip Hermans

1
Dans une méthode d'instance, voici comment appeler une méthode de classe:self.dynamicType.foo()

Réponses:


109

Eh bien, d'une part, l'équivalent Swift de [NSString class]is .self(voir la documentation Metatype , bien qu'elle soit assez mince).

En fait, NSString.classça ne marche même pas! Vous devez utiliser NSString.self.

let s = NSString.self
var str = s()
str = "asdf"

De même, avec une classe rapide, j'ai essayé ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… l'erreur dit:

L'exécution de Playground a échoué: erreur:: 16: 1: erreur: la construction d'un objet de type de classe 'X' avec une valeur de métatype nécessite un initialiseur '@required'

 Y().me()
 ^
 <REPL>:3:7: note: selected implicit initializer with type '()'
 class X {
       ^

Il m'a fallu un certain temps pour comprendre ce que cela signifie ... il s'avère qu'il veut que la classe ait un @required init()

class X {
    func me() {
        println("asdf")
    }

    required init () {

    }
}

let Y = X.self

// prints "asdf"
Y().me()

Certains documents appellent cela .Type, mais MyClass.Typeme donne une erreur dans la cour de récréation.


1
Merci pour votre lien vers la documentation Metatype! J'ai totalement négligé cet aspect des types, doh!
Erik le

14
Vous pouvez utiliser .Typeou .Protocoldans la déclaration de variable, par exemplelet myObject: MyObject.Type = MyObject.self
Sulthan

1
Sulthan: donc MyObject.Type est une déclaration mais MyObject.self est une méthode de fabrique (peut être appelée) et myObject est une variable contenant une référence à une méthode de fabrique. L'appel myObject () produirait une instance de la classe MyObject. Ce serait un meilleur exemple si le nom de la variable myObject était myObjectFactory?
bootchk

2
@avant requireddevrait être supprimé
fujianjin6471

49

Voici comment l'utiliser NSClassFromString. Vous devez connaître la superclasse avec laquelle vous allez vous retrouver. Voici une paire superclasse-sous-classe qui sait se décrire pour println:

@objc(Zilk) class Zilk : NSObject {
    override var description : String {return "I am a Zilk"}
}

@objc(Zork) class Zork : Zilk {
    override var description : String {return "I am a Zork"}
}

Notez l'utilisation de la @objsyntaxe spéciale pour dicter le nom en Objective-C de ces classes; c'est crucial, car sinon nous ne connaissons pas la chaîne munged qui désigne chaque classe.

Maintenant, nous pouvons utiliser NSClassFromStringpour créer la classe Zork ou la classe Zilk, car nous savons que nous pouvons le saisir en tant que NSObject et ne pas planter plus tard:

let aClass = NSClassFromString("Zork") as NSObject.Type
let anObject = aClass()
println(anObject) // "I am a Zork"

Et c'est réversible; println(NSStringFromClass(anObject.dynamicType))fonctionne également.


Version moderne:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }

10
Vote pour le @objc(ClassName)bit. Je connaissais l' @objcattribut mais pas que vous puissiez également donner un indice sur le nom de la classe.
Erik

1
Excellente solution qui fonctionne encore plus ou moins comme écrit 6 ans plus tard. Juste quelques ajustements mineurs demandés par le terrain de jeu: as! NSObject.Typedans la première ligne et aClass.init()dans la seconde
Kaji le

13

Si je lis bien la documentation, si vous traitez avec des instances et que vous voulez par exemple renvoyer une nouvelle instance du même type que l'objet que vous avez reçu et que le type peut être construit avec un init (), vous pouvez faire:

let typeOfObject = aGivenObject.dynamicType
var freshInstance = typeOfObject()

Je l'ai rapidement testé avec String:

let someType = "Fooo".dynamicType
let emptyString = someType()
let threeString = someType("Three")

qui a bien fonctionné.


1
Oui, dynamicTypefonctionne comme je m'y attendais. Cependant, je n'ai pas pu comparer les types. La vraie grande utilisation est avec les génériques, donc je pourrais avoir quelque chose comme Generic<T>et à l'intérieur if T is Double {...}. Il semble que ce ne soit pas possible par inadvertance.
Erik

1
@SiLo Avez-vous déjà trouvé un moyen de demander en général si deux objets sont de la même classe?
matt

1
@matt Pas élégamment, non je ne l'ai pas fait. Cependant, j'ai pu créer un Defaultableprotocole qui fonctionne de manière similaire au defaultmot clé de C # , et des extensions appropriées pour les types comme Stringet Int. En ajoutant la contrainte générique de T:Defaultable, je pourrais vérifier si l'argument est passé is T.default().
Erik

1
@SiLo Clever; J'aimerais voir ce code! Je suppose que cela contourne les étranges limitations sur l'utilisation de «est». J'ai déposé un bug sur ces limitations, ainsi que sur le manque général d'introspection de classe. J'ai fini par comparer des chaînes en utilisant NSStringFromClass, mais bien sûr, cela ne fonctionne que pour les descendants de NSObject.
mat

1
@matt Malheureusement, cela semble plus intelligent qu'il ne l'est vraiment parce que vous devez encore faire value is String.default()... etc, ce que vous finiriez par faire à la value is Stringplace.
Erik

13

Dans Swift 3

object.dynamicType

est obsolète.

Utilisez plutôt:

type(of:object)

7

Implémentation rapide de la comparaison des types

protocol Decoratable{}
class A:Decoratable{}
class B:Decoratable{}
let object:AnyObject = A()
object.dynamicType is A.Type//true
object.dynamicType is B.Type//false
object.dynamicType is Decoratable.Type//true

REMARQUE: notez qu'il fonctionne également avec les protocoles que l'objet peut étendre ou non


1

J'ai enfin quelque chose à travailler. C'est un peu paresseux mais même la route NSClassFromString () n'a pas fonctionné pour moi ...

import Foundation

var classMap = Dictionary<String, AnyObject>()

func mapClass(name: String, constructor: AnyObject) -> ()
{
    classMap[name] = constructor;
}

class Factory
{
    class func create(className: String) -> AnyObject?
    {
        var something : AnyObject?

        var template : FactoryObject? = classMap[className] as? FactoryObject

        if (template)
        {
            let somethingElse : FactoryObject = template!.dynamicType()

            return somethingElse
        }

        return nil
    }
}


 import ObjectiveC

 class FactoryObject : NSObject
{
    @required init() {}
//...
}

class Foo : FactoryObject
{
    class override func initialize()
    {
        mapClass("LocalData", LocalData())
    }
    init () { super.init() }
}

var makeFoo : AnyObject? = Factory.create("Foo")

et bingo, "makeFoo" contient une instance Foo.

L'inconvénient est que vos classes doivent dériver de FactoryObject et qu'elles DOIVENT avoir la méthode d'initialisation Obj-C + pour que votre classe soit automatiquement insérée dans la carte de classe par la fonction globale "mapClass".


1

Voici un autre exemple montrant l'implémentation de la hiérarchie de classes, similaire à la réponse acceptée, mis à jour pour la première version de Swift.

class NamedItem : NSObject {
    func display() {
        println("display")
    }

    required override init() {
        super.init()
        println("base")
    }
}

class File : NamedItem {
    required init() {
        super.init()
        println("folder")
    }
}

class Folder : NamedItem {
    required init() {
        super.init()
        println("file")
    }
}

let y = Folder.self
y().display()
let z = File.self
z().display()

Imprime ce résultat:

base
file
display
base
folder
display

2
Cette technique ne fonctionne pas correctement si la variable est le Type de la superclasse. Par exemple, donné var x: NamedItem.Type, si je l'assigne x = Folder.Type, x()renvoie un nouveau NamedItem, pas un Folder. Cela rend la technique inutile pour de nombreuses applications. Je considère cela comme un bug .
phatmann

1
En fait, vous pouvez faire ce que je pense que vous voulez en utilisant cette technique stackoverflow.com/questions/26290469/…
possen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.