Introspección de clase rápida y genéricos


121

Estoy tratando de crear dinámicamente un classtipo basado en instancias usando genéricos, sin embargo, me encuentro con dificultades con la introspección de clase.

Aquí están las preguntas:

  • ¿Hay un Swift equivalente a Obj-C's self.class?
  • ¿Hay alguna manera de instanciar una clase usando el AnyClassresultado de NSClassFromString?
  • ¿Hay alguna manera de obtener AnyClasso escribir información estrictamente de un parámetro genérico T? (Similar a la typeof(T)sintaxis de C # )

2
stackoverflow.com/a/24069875/292145 ofrece algunas sugerencias sobre la API de reflexión rápida.
— Klaas

55
Objective-C self.classse convertiría self.dynamicType.selfen la creencia Swift I
— Filip Hermans

1
En un método de instancia, aquí se explica cómo llamar a un método de clase:self.dynamicType.foo()

Respuestas:


109

Bueno, para uno, el equivalente rápido de [NSString class]es .self(ver documentos de Metatype , aunque son bastante delgados).

De hecho, ¡ NSString.classni siquiera funciona! Tienes que usar NSString.self.

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

Del mismo modo, con una clase rápida intenté ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm ... el error dice:

Error en la ejecución del patio de juegos: error:: 16: 1: error: la construcción de un objeto de clase tipo 'X' con un valor de metatipo requiere un inicializador '@required'

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

Me tomó un tiempo entender qué significa esto ... resulta que quiere que la clase tenga un @required init()

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

    required init () {

    }
}

let Y = X.self

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

Algunos de los documentos se refieren a esto como .Type, pero MyClass.Typeme da un error en el patio de recreo.


1
¡Gracias por su enlace a los documentos Metatype! Totalmente pasé por alto ese aspecto de los tipos, doh!
— Erik

14
Puede usar .Typeo .Protocolen declaración de variable, por ejemplolet myObject: MyObject.Type = MyObject.self
— Sulthan

1
Sulthan: entonces MyObject.Type es una declaración, pero MyObject.self es un método de fábrica (se puede llamar) y myObject es una variable que contiene una referencia a un método de fábrica. La llamada myObject () produciría una instancia de la clase MyObject. Sería un mejor ejemplo si el nombre de la variable myObject fuera myObjectFactory?
— bootchk

2
@antes requireddebe eliminarse
— fujianjin6471

49

Aquí se explica cómo usarlo NSClassFromString. Tienes que conocer la superclase de lo que vas a terminar. Aquí hay un par de superclase-subclase que sabe cómo describirse para 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"}
}

Observe el uso de la @objsintaxis especial para dictar el nombre modificado de Objective-C de estas clases; eso es crucial, porque de lo contrario no conocemos la cadena unida que designa a cada clase.

Ahora podemos usar NSClassFromStringpara hacer la clase Zork o la clase Zilk, porque sabemos que podemos escribirla como un NSObject y no bloquearnos más tarde:

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

Y es reversible; println(NSStringFromClass(anObject.dynamicType))También funciona.


Versión moderna:

    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
Votación por el @objc(ClassName)bit. Sabía sobre el @objcatributo, pero no porque también pudieras dar una pista sobre el nombre de la clase.
— Erik

1
Excelente solución que aún funciona más o menos como se escribió 6 años después. Solo un par de pequeños ajustes que pidió el patio de recreo: as! NSObject.Typeen la primera línea y aClass.init()en la segunda
— Kaji

13

Si estoy leyendo la documentación correctamente, si maneja instancias y, por ejemplo, desea devolver una nueva instancia del mismo Tipo que el objeto que se le ha dado y el Tipo se puede construir con un init (), puede hacer:

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

Lo probé rápidamente con String:

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

que funcionó bien


1
Sí, dynamicTypefunciona como esperaba allí. Sin embargo, no he podido comparar tipos. El gran uso real es con los genéricos, por lo que podría tener algo así Generic<T>como dentro if T is Double {...}. Parece que no es posible desafortunadamente.
— Erik

1
@SiLo ¿Alguna vez encontró una manera de preguntar en general si dos objetos son de la misma clase?
— mate

1
@matt No con elegancia, no, no lo hice. Sin embargo, pude crear un Defaultableprotocolo que funciona de manera similar a la defaultpalabra clave de C # y extensiones adecuadas para tipos como Stringy Int. Al agregar la restricción genérica de T:Defaultable, podría verificar si el argumento pasó is T.default().
— Erik

1
@SiLo Clever; ¡Me gustaría ver ese código! Supongo que esto evita las extrañas limitaciones en el uso de "es". He presentado un error en esas limitaciones, y también en la falta general de introspección de clase. Terminé comparando cadenas usando NSStringFromClass, pero por supuesto eso solo funciona para descendientes de NSObject.
— mate

1
@matt Desafortunadamente, suena más inteligente de lo que realmente es porque todavía tienes que hacer value is String.default()... etc., lo que terminarías haciendo en su value is Stringlugar.
— Erik

13

En Swift 3

object.dynamicType

es obsoleto.

En su lugar use:

type(of:object)

7

Implementación rápida de tipos de comparación

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

NOTA: Tenga en cuenta que también funciona con protocolos que el objeto puede o no extender


1

Finalmente conseguí algo para trabajar. Es un poco vago, pero incluso la ruta NSClassFromString () no funcionó para mí ...

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")

y bingo, "makeFoo" contiene una instancia de Foo.

La desventaja es que sus clases deben ser derivadas de FactoryObject y DEBEN tener el método de inicialización Obj-C + para que su clase se inserte automáticamente en el mapa de clase mediante la función global "mapClass".


1

Aquí hay otro ejemplo que muestra la implementación de la jerarquía de clases, similar a la respuesta aceptada, actualizada para la primera versión 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 este resultado:

base
file
display
base
folder
display

2
Esta técnica no funciona correctamente si la variable es el tipo de la superclase. Por ejemplo, dado var x: NamedItem.Type, si lo asigno x = Folder.Type, x()devuelve un nuevo NamedItem, no un Folder. Esto hace que la técnica sea inútil para muchas aplicaciones. Considero que esto es un error .
— phatmann

1
En realidad, puedes hacer lo que creo que quieres usando esta técnica stackoverflow.com/questions/26290469/…
— Posible el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.