Obiettivo C Introspezione / Riflessione

Esiste un metodo, una funzione, un’API, un metodo comunemente accettato, ecc. Per scaricare il contenuto di un object istanziato nell’Objective C, in particolare nell’ambiente Apple Cocoa / Cocoa-Touch?

Voglio essere in grado di fare qualcosa del genere

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

e visualizzare i nomi e i valori delle variabili di istanza dell’object, insieme ai metodi disponibili per chiamare in fase di esecuzione. Idealmente in un formato facile da leggere.

Per gli sviluppatori che hanno familiarità con PHP, sto fondamentalmente cercando l’equivalente delle funzioni di reflection ( var_dump() , get_class_methods() ) e OO Reflection API.

AGGIORNAMENTO: Chiunque voglia fare questo tipo di cose potrebbe voler controllare il wrapper ObjC di Mike Ash per il runtime Objective-C .

Questo è più o meno come lo faresti:

 #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); } 

Da lì, è facile ottenere i valori effettivi delle proprietà di un'istanza, ma è necessario verificare se siano tipi o oggetti primitivi, quindi ero troppo pigro per inserirli. Potresti anche scegliere di eseguire la scansione della catena di ereditarietà in ottenere tutte le proprietà definite su un object. Poi ci sono metodi definiti su categorie e altro ... Ma quasi tutto è facilmente disponibile.

Ecco un estratto di ciò che il codice di cui sopra scarica per 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 ); } 

A parte il metodo di description (come .toString () in Java), non ho sentito di uno che è stato integrato, ma non sarebbe troppo difficile crearne uno. L’Objective-C Runtime Reference ha un sacco di funzioni che è ansible utilizzare per ottenere informazioni sulle variabili di istanza di un object, i metodi, le proprietà, ecc.

Ecco cosa sto attualmente usando per stampare automaticamente le variabili di class, in una libreria per eventuali pubblicazioni pubbliche – funziona scaricando tutte le proprietà dalla class di istanza fino a tutto il backup dell’albero di ereditarietà. Grazie a KVC non è necessario preoccuparsi se una proprietà è di tipo primitivo o no (per la maggior parte dei tipi).

 // 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]]]; } 

Ho apportato un paio di modifiche al codice di Kendall per la stampa dei valori delle proprietà, che mi è stato molto utile. L’ho definito come metodo di istanza anziché come metodo di class, in quanto è così che la ricorsione della superclass lo chiama. Ho anche aggiunto la gestione delle eccezioni per le proprietà non conformi a KVO e ho aggiunto interruzioni di riga all’output per renderlo più facile da leggere (e 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; } 

Onestamente, lo strumento giusto per questo lavoro è il debugger di Xcode. Ha tutte queste informazioni facilmente accessibili in modo visivo. Prenditi del tempo per imparare come usarlo, è uno strumento davvero potente. Ulteriori informazioni: Guida al debug di Xcode .

Ho fatto di questo cocoapod, https://github.com/neoneye/autodescribe

Ho modificato il codice di Christopher Pickslay e ne ho fatto una categoria su NSObject e ho aggiunto anche un unittest ad esso. Ecco come 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