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 # )
Erik
fuente
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.

Jiaaro
fuente
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
    }
mate
fuente
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

monkeydom
fuente
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)
J.beenie
fuente
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

eonista
fuente
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".

Martin-Gilles Lavoie
fuente
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
poseer
fuente
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