¿Existe un método, función, API, forma comúnmente aceptada, etc., incorporada para volcar el contenido de un objeto instanciado en Objective-C, específicamente en el entorno Cocoa / Cocoa-Touch de Apple?
Quiero poder hacer algo como
MyType *the_thing = [[MyType alloc] init];
NSString *the_dump = [the_thing dump]; //pseudo code
NSLog("Dumped Contents: %@", the_dump);
y mostrar los nombres y valores de las variables de instancia del objeto, junto con cualquier método disponible para llamar en tiempo de ejecución. Idealmente en un formato fácil de leer.
Para los desarrolladores familiarizados con PHP, básicamente estoy buscando el equivalente de las funciones de reflexión ( var_dump()
, get_class_methods()
) y la API de reflexión de OO.
objective-c
cocoa
xcode
reflection
introspection
Alan Storm
fuente
fuente
Respuestas:
ACTUALIZACIÓN: Cualquiera que desee hacer este tipo de cosas puede querer consultar el contenedor ObjC de Mike Ash para el tiempo de ejecución de Objective-C .
Así es más o menos cómo lo haría:
#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 obtener los valores reales de las propiedades de una instancia, pero debe verificar si son tipos u objetos primitivos, así que fui demasiado perezoso para ponerlo. También puede optar por escanear la cadena de herencia para obtener todas las propiedades definidas en un objeto. Luego están los métodos definidos por categorías, y más ... Pero casi todo está disponible.
Aquí hay un extracto de lo que el código anterior volca para 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 ); }
fuente
NSObject
categoría). Sin embargo, hay quefree()
tusivars
,properties
ymethods
matrices, o de lo contrario les está goteando.description
Aparte del método (como .toString () en Java), no he oído hablar de uno que esté integrado, pero no sería demasiado difícil crear uno. Objective-C Runtime Reference tiene un montón de funciones que puede utilizar para obtener información sobre las variables de instancia, métodos, propiedades, etc. de un objeto.fuente
Esto es lo que estoy usando actualmente para imprimir automáticamente las variables de clase, en una biblioteca para un eventual lanzamiento público: funciona volcando todas las propiedades de la clase de instancia hasta el final del árbol de herencia. Gracias a KVC, no necesita preocuparse 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]]]; }
fuente
Hice un par de ajustes en el código de Kendall para imprimir valores de propiedad, lo que me resultó muy útil. Lo definí como un método de instancia en lugar de un método de clase, ya que así lo llama la recursividad de superclase. También agregué manejo de excepciones para propiedades que no cumplen con KVO y agregué saltos de línea a la salida para facilitar la lectura (y la diferencia):
-(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; }
fuente
Honestamente, la herramienta adecuada para este trabajo es el depurador de Xcode. Tiene toda esta información fácilmente accesible de forma visual. Tómate el tiempo para aprender a usarlo, es una herramienta realmente poderosa.
Más información:
Usando el depurador
Guía de depuración de Xcode obsoleta : archivada por Apple
Acerca de la depuración con Xcode : archivado por Apple
Acerca de LLDB y depuración : archivado por Apple
Depuración con GDB : archivado por Apple
Guía de depuración de SpriteKit : archivada por Apple
Temas de programación de depuración para Core Foundation : archivado por Apple
fuente
He hecho cocoapod con esto, https://github.com/neoneye/autodescribe
Modifiqué el código de Christopher Pickslay y lo convertí en una categoría en NSObject y también le agregué una prueba unitaria. Así es como se usa:
@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
fuente
Estoy confundido con Introspección y Refección antes, así que obtenga información a continuación.
La introspección es la capacidad que tiene el objeto de verificar qué tipo es, o el protocolo al que se conforma, o el selector al que puede responder. La API de objc como
isKindOfClass
/isMemberOfClass
/conformsToProtocol
/respondsToSelector
etc.La capacidad de reflexión va más allá de la introspección, no solo puede obtener información del objeto, sino que también puede operar metadatos, propiedades y funciones del objeto. como
object_setClass
puede modificar el tipo de objeto.fuente