¿Swift admite la reflexión?

113

¿Swift admite la reflexión? por ejemplo, ¿hay algo como valueForKeyPath:y setValue:forKeyPath:para objetos Swift?

En realidad, ¿tiene siquiera un sistema de tipos dinámico, algo así como obj.classen Objective-C?

Khanh Nguyen
fuente
1
Creé una clase de ayuda para reflexionar en Swift. Puede encontrarlo en: github.com/evermeer/EVReflection
Edwin Vermeer
2
Han eliminado el reflejo en Swift 2.0. Así es como estoy enumerando atributos y valores Enlace
mohacs

Respuestas:

85

Parece que está el comienzo de algún apoyo de reflexión:

class Fruit {
    var name="Apple"
}

reflect(Fruit()).count         // 1
reflect(Fruit())[0].0          // "name"
reflect(Fruit())[0].1.summary  // "Apple"

De mchambers gist, aquí: https://gist.github.com/mchambers/fb9da554898dae3e54f2

stevex
fuente
5
Bueno, no consideraría esto un reflejo real. Por un lado, es de solo lectura. Me parece que es solo un truco para habilitar la depuración en Xcode. En Mirrorrealidad, Protocol cita la palabra IDEvarias veces.
Sulthan
7
Y funciona solo para propiedades. Sin reflexión de método.
Sulthan
11
Autor de la esencia del registro. Escribí esto en el laboratorio de Swift en la WWDC, pensé que compartiría el resto. Como todos se dieron cuenta, los ingenieros con los que hablé confirmaron que la función reflect () existe para admitir Playground. Pero aún puedes divertirte un poco con él :) aquí he pirateado un pequeño serializador de modelos usándolo. Péguelo
Marc Chambers
1
Eche un vistazo a la respuesta stackoverflow.com/a/25345461/292145 para descubrir cómo _stdlib_getTypeNamepuede ayudar.
Klaas
1
Aquí hay una clase que reflejará las clases base y los opcionales (no los tipos) y tiene soporte para NSCoding y analizar desde y hacia un diccionario: github.com/evermeer/EVCloudKitDao/blob/master/AppMessage/…
Edwin Vermeer
44

Si una clase se extiende NSObject, entonces toda la introspección y el dinamismo de Objective-C funcionan. Esto incluye:

  • La capacidad de preguntar a una clase sobre sus métodos y propiedades, y de invocar métodos o establecer propiedades.
  • La capacidad de intercambiar implementaciones de métodos. (agregue funcionalidad a todas las instancias).
  • La capacidad de generar y asignar una nueva subclase sobre la marcha. (agregar funcionalidad a una instancia determinada)

Una deficiencia de esta funcionalidad es la compatibilidad con los tipos de valores opcionales de Swift. Por ejemplo, las propiedades Int se pueden enumerar y modificar, pero Int? las propiedades no pueden. Los tipos opcionales se pueden enumerar parcialmente usando reflect / MirrorType, pero aún no se modifican.

Si una clase no se extiende NSObject, entonces solo funciona la nueva reflexión muy limitada (¿y en progreso?) (Ver reflect / MirrorType), que agrega una capacidad limitada para preguntar a una instancia sobre su clase y propiedades, pero ninguna de las características adicionales anteriores. .

Cuando no se extiende NSObject, o se usa la directiva '@objc', Swift utiliza de forma predeterminada el envío basado en estática y vtable. Sin embargo, esto es más rápido, en ausencia de una máquina virtual, no permite la interceptación del método en tiempo de ejecución. Esta interceptación es una parte fundamental de Cocoa y es necesaria para los siguientes tipos de características:

  • Observadores de propiedad elegantes de Cocoa. (Los observadores de propiedades están integrados en el lenguaje Swift).
  • Aplicación no invasiva de preocupaciones transversales como registro, gestión de transacciones (es decir, programación orientada a aspectos).
  • Proxies, reenvío de mensajes, etc.

Por lo tanto, se recomienda que las clases en aplicaciones Cocoa / CocoaTouch implementadas con Swift:

  • Amplíe desde NSObject. El nuevo diálogo de clase en Xcode se dirige en esta dirección.
  • Cuando la sobrecarga de un despacho dinámico conduce a problemas de rendimiento, se puede utilizar el despacho estático, en bucles estrechos con llamadas a métodos con cuerpos muy pequeños, por ejemplo.

Resumen:

  • Swift puede comportarse como C ++, con un envío rápido estático / vtable y una reflexión limitada. Esto lo hace adecuado para aplicaciones de bajo nivel o de alto rendimiento, pero sin la complejidad, la curva de aprendizaje o el riesgo de error asociados con C ++
  • Si bien Swift es un lenguaje compilado, el estilo de mensajería de la invocación de métodos agrega la introspección y el dinamismo que se encuentran en lenguajes modernos como Ruby y Python, al igual que Objective-C, pero sin la sintaxis heredada de Objective-C.

Datos de referencia: sobrecarga de ejecución para invocaciones de métodos:

  • estático: <1.1ns
  • vtable: ~ 1.1ns
  • dinámico: ~ 4.9ns

(el rendimiento real depende del hardware, pero las proporciones seguirán siendo similares).

Además, el atributo dinámico nos permite instruir explícitamente a Swift de que un método debe usar envío dinámico y, por lo tanto, admitirá la interceptación.

public dynamic func foobar() -> AnyObject {
}
Jasper Blues
fuente
2
Incluso usando técnicas de Objective-C, parece no funcionar para tipos Swift opcionales. Sugeriría señalar esta limitación en la respuesta a menos que me esté perdiendo un truco.
Whitneyland
8

La documentación habla de un sistema de tipos dinámicos, principalmente sobre

Type y dynamicType

Ver tipo de metatipo (en la referencia del lenguaje)

Ejemplo:

var clazz = TestObject.self
var instance: TestObject = clazz()

var type = instance.dynamicType

println("Type: \(type)") //Unfortunately this prints only "Type: Metatype"

Ahora asumiendo que se TestObjectextiendeNSObject

var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!") //prints "yes!"
}

Actualmente, no hay una reflexión implementada.

EDITAR: Aparentemente estaba equivocado, vea la respuesta de stevex. Hay una reflexión simple de solo lectura para las propiedades incorporadas, probablemente para permitir que los IDE inspeccionen el contenido del objeto.

Sulthan
fuente
6

Parece que una API de reflexión Swift no es una alta prioridad para Apple en este momento. Pero además de la respuesta de @stevex, hay otra función en la biblioteca estándar que ayuda.

A partir de la versión beta 6, se _stdlib_getTypeNameobtiene el nombre de tipo alterado de una variable. Pegue esto en un patio de recreo vacío:

import Foundation

class PureSwiftClass {
}

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")

La salida es:

TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

La entrada del blog de Ewan Swick ayuda a descifrar estas cadenas:

por ejemplo, _TtSirepresenta el Inttipo interno de Swift .

Mike Ash tiene una excelente entrada de blog que cubre el mismo tema .

Klaas
fuente
@aleclarson Sí, eso también es bastante útil.
Klaas
1
¿No son esas API privadas? ¿Apple aprobará la aplicación si se utilizan?
Eduardo Costa
@EduardoCosta sí, seguro. Son privados. Solo los uso para depurar compilaciones.
Klaas
Aquí hay un enlace actualizado al artículo del blog de Ewan Swick: eswick.com/2014/06/08/Inside-Swift
RenniePet
5

Es posible que desee considerar el uso de toString () en su lugar. Es público y funciona igual que _stdlib_getTypeName () con la diferencia de que también funciona en AnyClass , por ejemplo, en un patio de recreo.

class MyClass {}

toString(MyClass.self) // evaluates to "__lldb_expr_49.MyClass"
entrada de seda
fuente
1

Sin reflectpalabra clave en Swift 5, ahora puede usar

struct Person {
    var name="name"
    var age = 15
}

var me = Person()
var mirror = Mirror(reflecting: me)

for case let (label?, value) in mirror.children {
    print (label, value)
}
Jacky
fuente
¿Por qué no se vota a favor? Esto es muy útil. Voy a aplicarlo para la jsondeserialización
javadba