2010-02-19 23 views
77

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

voglio essere in grado di fare qualcosa di simile

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

e hanno i nomi delle variabili di istanza dell'oggetto e dei valori visualizzati, insieme a tutti i metodi disponibili per chiamare in fase di esecuzione. Idealmente in un formato facile da leggere.

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

+0

Mi chiedo la stessa domanda ci sono pochi giorni. Grazie per questa domanda. –

+7

Sì, ottima domanda. Uno dei maggiori vantaggi di ObjC rispetto ad altri linguaggi simili è il suo sorprendente sistema di runtime dinamico che ti permette di fare cose fantastiche come questa. Sfortunatamente, le persone lo usano raramente al massimo delle sue potenzialità, quindi complimenti per aver insegnato alla comunità SO con la tua domanda. – rpj

+0

Ho creato una libreria leggera per la gestione della riflessione [OSReflectionKit] (https://github.com/iAOS/OSReflectionKit). Usando questa libreria puoi chiamare semplicemente [the_thing fullDescription]. –

risposta

111

UPDATE: Chiunque cerchi di fare questo genere di cose potrebbe voler controllare Mike Ash's ObjC wrapper for the Objective-C runtime.

Questo è più o meno come ci si va su di esso:

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

Da lì, è facile ottenere i valori reali di proprietà di un esempio, ma è necessario controllare per vedere se sono primitivi tipi o oggetti, quindi ero troppo pigro per inserirli. Potresti anche scegliere di eseguire la scansione della catena di ereditarietà per ottenere tutte le proprietà definite su un oggetto. 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 discariche 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 
    ); 
} 
+6

+1 è praticamente come lo farei (rendilo una categoria 'NSObject'). Tuttavia, devi 'liberare()' le tue 'ivars',' properties' e 'methods' matrici, oppure le perdi. –

+0

Woops, l'ho dimenticato. Mettili adesso. Grazie. – Felixyz

+4

Non vediamo i valori degli ivar? –

12

Breve del metodo description (come .toString() in Java), non ho sentito di uno che è stato integrato, ma non sarebbe troppo difficile crearne uno. The Objective-C Runtime Reference ha un sacco di funzioni che è possibile utilizzare per ottenere informazioni sulle variabili di un oggetto di istanza, metodi, proprietà, ecc

+0

+1 per le informazioni, solo il tipo di cosa che stavo cercando. Detto questo, speravo in una soluzione pre-costruita, dato che sono abbastanza nuovo per il linguaggio che qualsiasi cosa avessi rapidamente messo insieme potrebbe perdere un po 'di sottilmente il linguaggio. –

+6

Se hai intenzione di minimizzare una risposta, ti preghiamo di avere la cortesia di lasciare un commento perché. –

8

Ecco cosa Attualmente sto usando per stampare automaticamente variabili di classe, in una biblioteca per l'eventuale rilascio pubblico - funziona allo scarico di rifiuti tutti oggetti di la classe di istanza per 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).

+0

+1 molto utile, anche se in parte risponde alla domanda. Aggiungerai un commento qui quando rilasci la libreria? – Felixyz

+0

Sì, aggiungerò a questo ... –

+0

+1 Questo è fantastico, grazie mille. – prototypical

3

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: Xcode Debugging Guide.

+4

+1 per buone informazioni, ma a volte è più veloce scaricare lo stato di un oggetto in due punti e diffare l'output rispetto a quello che inizia nello stato di un programma in un singolo punto nel tempo. –

4

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 classe, in quanto è così che la ricorsione della superclasse lo chiama. Ho anche aggiunto eccezione movimentazione per proprietà non KVO conformi, e la linea aggiunto rompe all'uscita per rendere più facile la lettura (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; 
} 
+0

GRANDI INFO !!! - Hai idea di come impostare la proprietà effettiva dopo averla trovata utilizzando Reflection? Ho una domanda qui: http://stackoverflow.com/q/25538890/1735836 – Patricia

1

Ho fatto cocoapod fuori di questo, 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