2010-02-19 31 views
77

¿Existe un método, función, API, forma comúnmente aceptada, etc. incorporado para volcar el contenido de un objeto instanciado en el Objetivo C, específicamente en el entorno Cocoa/Cocoa-Touch de Apple?Objetivo C Introspección/Reflexión

quiero ser capaz de hacer algo como

MyType *the_thing = [[MyType alloc] init]; 
NSString *the_dump = [the_thing dump]; //pseudo code 
NSLog("Dumped Contents: %@", the_dump); 

y tienen nombres de variables de instancia del objeto y los valores que aparecen, junto con todos los métodos disponibles para llamar en tiempo de ejecución. Idealmente en un formato fácil de leer.

Para desarrolladores familiarizados con PHP, básicamente estoy buscando el equivalente de las funciones de reflexión (var_dump(), get_class_methods()) y la OO Reflection API.

+0

Me pregunto la misma pregunta que hay algunos días. gracias por esta pregunta. –

+7

Sí, gran pregunta. Una de las mayores ventajas de ObjC sobre otros lenguajes similares es su increíble sistema de tiempo de ejecución dinámico que le permite hacer cosas increíbles como esta. Desafortunadamente, las personas rara vez lo utilizan en todo su potencial, así que felicitaciones por enseñarle a la comunidad SO con su pregunta. – rpj

+0

He creado una biblioteca liviana para tratar con la reflexión [OSReflectionKit] (https://github.com/iAOS/OSReflectionKit). Usando esta biblioteca, puede simplemente llamar a [the_thing fullDescription]. –

Respuesta

111

ACTUALIZACIÓN: Cualquiera que quiera hacer este tipo de cosas puede ser que desee comprobar hacia fuera Mike Ash's ObjC wrapper for the Objective-C runtime.

Esto es más o menos la forma en que iría al respecto:

#import <objc/runtime.h> 

. . . 

-(void)dumpInfo 
{ 
    Class clazz = [self class]; 
    u_int count; 

    Ivar* ivars = class_copyIvarList(clazz, &count); 
    NSMutableArray* ivarArray = [NSMutableArray arrayWithCapacity:count]; 
    for (int i = 0; i < count ; i++) 
    { 
     const char* ivarName = ivar_getName(ivars[i]); 
     [ivarArray addObject:[NSString stringWithCString:ivarName encoding:NSUTF8StringEncoding]]; 
    } 
    free(ivars); 

    objc_property_t* properties = class_copyPropertyList(clazz, &count); 
    NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count]; 
    for (int i = 0; i < count ; i++) 
    { 
     const char* propertyName = property_getName(properties[i]); 
     [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]]; 
    } 
    free(properties); 

    Method* methods = class_copyMethodList(clazz, &count); 
    NSMutableArray* methodArray = [NSMutableArray arrayWithCapacity:count]; 
    for (int i = 0; i < count ; i++) 
    { 
     SEL selector = method_getName(methods[i]); 
     const char* methodName = sel_getName(selector); 
     [methodArray addObject:[NSString stringWithCString:methodName encoding:NSUTF8StringEncoding]]; 
    } 
    free(methods); 

    NSDictionary* classDump = [NSDictionary dictionaryWithObjectsAndKeys: 
           ivarArray, @"ivars", 
           propertyArray, @"properties", 
           methodArray, @"methods", 
           nil]; 

    NSLog(@"%@", classDump); 
} 

A partir de ahí, es fácil de obtener los valores reales de las propiedades de un ejemplo, pero hay que comprobar para ver si son primitiva tipos u objetos, por lo que era demasiado flojo para ponerlo. También puede optar por escanear la cadena de herencia para obtener todas las propiedades definidas en un objeto. Luego hay métodos definidos en categorías, y más ... Pero casi todo está disponible.

He aquí un extracto de lo que el código anterior deja por UILabel:

{ 
    ivars =  (
     "_size", 
     "_text", 
     "_color", 
     "_highlightedColor", 
     "_shadowColor", 
     "_font", 
     "_shadowOffset", 
     "_minFontSize", 
     "_actualFontSize", 
     "_numberOfLines", 
     "_lastLineBaseline", 
     "_lineSpacing", 
     "_textLabelFlags" 
    ); 
    methods =  (
     rawSize, 
     "setRawSize:", 
     "drawContentsInRect:", 
     "textRectForBounds:", 
     "textSizeForWidth:", 
     . . . 
    ); 
    properties =  (
     text, 
     font, 
     textColor, 
     shadowColor, 
     shadowOffset, 
     textAlignment, 
     lineBreakMode, 
     highlightedTextColor, 
     highlighted, 
     enabled, 
     numberOfLines, 
     adjustsFontSizeToFitWidth, 
     minimumFontSize, 
     baselineAdjustment, 
     "_lastLineBaseline", 
     lineSpacing, 
     userInteractionEnabled 
    ); 
} 
+6

+1 eso es más o menos cómo lo haría (convertirlo en una categoría 'NSObject'). Sin embargo, tienes que 'free()' tus arreglos 'ivars',' properties', y 'methods', o si no los estás filtrando. –

+0

Woops, se olvidó de eso. Ponlos ahora. Gracias. – Felixyz

+4

¿No podemos ver los valores de los ivars? –

12

Método corto (description) (como .toString() en Java), no he oído hablar de uno que esté integrado, pero no sería demasiado difícil crear uno. The Objective-C Runtime Reference tiene un montón de funciones que se pueden utilizar para obtener información sobre las variables de instancia de un objeto, métodos, propiedades, etc.

+0

+1 para obtener información, justo el tipo de cosa que estaba buscando. Dicho esto, esperaba una solución preconstruida, ya que soy lo suficientemente nuevo para el lenguaje que todo lo que rápidamente podría hackear juntos podría pasar por alto algunos sutilmente el lenguaje. –

+6

Si va a rechazar una respuesta, tenga la cortesía de dejar un comentario por qué. –

8

Esto es lo que estoy utilizando actualmente para imprimir automáticamente las variables de clase, en una biblioteca para su publicación posterior - que funciona por el dumping todas las propiedades de la clase de instancia hace una copia de seguridad del árbol de herencia. Gracias a KVC no es necesario que se preocupe si una propiedad es de tipo primitivo o no (para la mayoría de los tipos).

// Finds all properties of an object, and prints each one out as part of a string describing the class. 
+ (NSString *) autoDescribe:(id)instance classType:(Class)classType 
{ 
    NSUInteger count; 
    objc_property_t *propList = class_copyPropertyList(classType, &count); 
    NSMutableString *propPrint = [NSMutableString string]; 

    for (int i = 0; i < count; i++) 
    { 
     objc_property_t property = propList[i]; 

     const char *propName = property_getName(property); 
     NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding]; 

     if(propName) 
     { 
      id value = [instance valueForKey:propNameString]; 
      [propPrint appendString:[NSString stringWithFormat:@"%@=%@ ; ", propNameString, value]]; 
     } 
    } 
    free(propList); 


    // Now see if we need to map any superclasses as well. 
    Class superClass = class_getSuperclass(classType); 
    if (superClass != nil && ! [superClass isEqual:[NSObject class]]) 
    { 
     NSString *superString = [self autoDescribe:instance classType:superClass]; 
     [propPrint appendString:superString]; 
    } 

    return propPrint; 
} 

+ (NSString *) autoDescribe:(id)instance 
{ 
    NSString *headerString = [NSString stringWithFormat:@"%@:%p:: ",[instance class], instance]; 
    return [headerString stringByAppendingString:[self autoDescribe:instance classType:[instance class]]]; 
} 
+0

+1 muy útil, aunque solo responde la pregunta en parte. ¿Agregarás un comentario aquí cuando abras la biblioteca? – Felixyz

+0

Sí, añadiré a esto ... –

+0

+1 Esto es genial, muchas gracias. – prototypical

3

Honestamente, la herramienta correcta para este trabajo es el depurador de Xcode. Tiene toda esta información fácilmente accesible de una manera visual. Tómese el tiempo para aprender a usarlo, es una herramienta realmente poderosa. Más información: Xcode Debugging Guide.

+4

+1 para obtener una buena información, pero a veces es más rápido volcar el estado de un objeto en dos puntos y diferir el resultado de lo que está comenzando en el estado de un programa en un único punto en el tiempo. –

4

Hice un par de ajustes al código de Kendall para imprimir los valores de las propiedades, lo cual fue muy útil para mí. Lo definí como un método de instancia en lugar de un método de clase, así es como lo llama la recursión de la clase superior. También he añadido la gestión de excepciones de propiedades no MVA-compatibles, y la línea añadido rompe a la salida para que sea más fácil de leer (y diferencias):

-(NSString *) autoDescribe:(id)instance classType:(Class)classType 
{ 
    NSUInteger count; 
    objc_property_t *propList = class_copyPropertyList(classType, &count); 
    NSMutableString *propPrint = [NSMutableString string]; 

    for (int i = 0; i < count; i++) 
    { 
     objc_property_t property = propList[i]; 

     const char *propName = property_getName(property); 
     NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding]; 

     if(propName) 
     { 
     @try { 
      id value = [instance valueForKey:propNameString]; 
      [propPrint appendString:[NSString stringWithFormat:@"%@=%@\n", propNameString, value]]; 
     } 
     @catch (NSException *exception) { 
      [propPrint appendString:[NSString stringWithFormat:@"Can't get value for property %@ through KVO\n", propNameString]]; 
     } 
     } 
    } 
    free(propList); 


    // Now see if we need to map any superclasses as well. 
    Class superClass = class_getSuperclass(classType); 
    if (superClass != nil && ! [superClass isEqual:[NSObject class]]) 
    { 
     NSString *superString = [self autoDescribe:instance classType:superClass]; 
     [propPrint appendString:superString]; 
    } 

    return propPrint; 
} 
+0

GRAN INFORMACIÓN !!! - ¿Tiene alguna idea de cómo establecer la propiedad real una vez que la ha encontrado utilizando Reflection? Tengo una pregunta aquí: http://stackoverflow.com/q/25538890/1735836 – Patricia

1

he hecho cocoapod fuera de esto, https://github.com/neoneye/autodescribe

He modificado el código de Christopher Pickslay y lo he convertido en una categoría en NSObject y también he añadido una prueba de unidad.Aquí es cómo usarlo:

@interface TestPerson : NSObject 

@property (nonatomic, strong) NSString *firstName; 
@property (nonatomic, strong) NSString *lastName; 
@property (nonatomic, strong) NSNumber *age; 

@end 

@implementation TestPerson 

// empty 

@end 

@implementation NSObject_AutoDescribeTests 

-(void)test0 { 
    TestPerson *person = [TestPerson new]; 
    person.firstName = @"John"; 
    person.lastName = @"Doe"; 
    person.age = [NSNumber numberWithFloat:33.33]; 
    NSString *actual = [person autoDescribe]; 
    NSString *expected = @"firstName=John\nlastName=Doe\nage=33.33"; 
    STAssertEqualObjects(actual, expected, nil); 
} 

@end