Objetivo C Introspecção / Reflexão

Existe um método embutido, function, API, forma comumente aceita, etc. para despejar o conteúdo de um object instanciado no Objective C, especificamente no ambiente Cocoa / Cocoa-Touch da Apple?

Eu quero ser capaz de fazer algo como

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

e ter os nomes e valores da variável de instância do object exibidos, junto com quaisquer methods disponíveis para chamar no tempo de execução. Idealmente em um formato fácil de ler.

Para desenvolvedores familiarizados com PHP, estou basicamente procurando o equivalente das funções de reflection ( var_dump() , get_class_methods() ) e da API de reflection OO.

ATUALIZAÇÃO: Qualquer um que queira fazer esse tipo de coisa pode querer dar uma olhada no wrapper ObjC do Mike Ash para o tempo de execução do Objective-C .

Isso é mais ou menos como você faria:

 #import  . . . -(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 daí, é fácil obter os valores reais das propriedades de uma instância, mas você precisa verificar se eles são tipos ou objects primitivos, por isso eu estava com preguiça de colocá-la. Você também pode optar por verificar a cadeia de inheritance para obtenha todas as propriedades definidas em um object. Depois, existem methods definidos em categorias e mais ... Mas quase tudo está prontamente disponível.

Aqui está um trecho do que o código acima descarta para o 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 ); } 

Curto do método de description (como .toString () em Java), eu não ouvi falar de um que foi construído, mas não seria muito difícil criar um. A Referência de Tempo de Execução do Objective-C possui um monte de funções que você pode usar para obter informações sobre variables ​​de instância, methods, propriedades, etc. de um object.

Aqui está o que eu estou usando atualmente para imprimir automaticamente as variables ​​de class, em uma biblioteca para eventual lançamento público – ele funciona despejando todas as propriedades da class de instância todo o caminho de volta até a tree de inheritance. Graças ao KVC você não precisa se preocupar se uma propriedade é um tipo primitivo ou não (para a maioria dos 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]]]; } 

Fiz alguns ajustes no código de Kendall para imprimir valores de propriedade, o que foi muito útil para mim. Eu defini como um método de instância em vez de um método de class, como é assim que a recursion da superclass o chama. Também adicionei o tratamento de exceções para propriedades não compatíveis com KVO e adicionei quebras de linha na saída para facilitar a leitura (e o diff):

 -(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; } 

Honestamente, a ferramenta certa para este trabalho é o depurador do Xcode. Tem toda essa informação facilmente acessível de forma visual. Aproveite o tempo para aprender a usá-lo, é uma ferramenta muito poderosa. Mais informações: Guia de Depuração do Xcode .

Eu fiz cocoapod fora deste, https://github.com/neoneye/autodescribe

Eu modifiquei o código de Christopher Pickslay e fiz dele uma categoria no NSObject e também adicionei um teste de unidade a ele. Aqui está como usá-lo:

 @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