Ziel-C Selbstbeobachtung / Reflexion

79

Gibt es eine integrierte Methode, Funktion, API, allgemein akzeptierte Methode usw., um den Inhalt eines instanziierten Objekts in Objective-C zu sichern, insbesondere in Apples Cocoa / Cocoa-Touch-Umgebung?

Ich möchte in der Lage sein, so etwas zu tun

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

Außerdem werden die Namen und Werte der Instanzvariablen des Objekts sowie alle Methoden angezeigt, die zur Laufzeit aufgerufen werden können. Idealerweise in einem leicht lesbaren Format.

Für Entwickler, die mit PHP vertraut sind, suche ich grundsätzlich nach dem Äquivalent der Reflektionsfunktionen ( var_dump(), get_class_methods()) und der OO Reflection API.

Alan Storm
quelle
Ich frage mich die gleiche Frage, es gibt einige Tage. Vielen Dank für diese Frage.
Yannick Loriot
7
Ja, tolle Frage. Einer der größten Vorteile von ObjC gegenüber anderen ähnlichen Sprachen ist das erstaunliche dynamische Laufzeitsystem, mit dem Sie großartige Dinge wie diese ausführen können. Leider nutzen die Leute es selten in vollem Umfang, daher ein großes Lob dafür, dass Sie der SO-Community mit Ihrer Frage davon erzählen.
RPJ
Ich habe eine leichte Bibliothek für den Umgang mit Reflection OSReflectionKit erstellt . Mit dieser Bibliothek können Sie einfach [the_thing fullDescription] aufrufen.
Alexandre OS
GROSSE Frage !! - Haben Sie eine Idee, wie Sie die tatsächliche Eigenschaft festlegen können, wenn Sie sie mithilfe von Reflection gefunden haben? Ich habe hier eine Frage: stackoverflow.com/q/25538890/1735836
Patricia

Antworten:

112

UPDATE: Jeder, der solche Dinge tun möchte, sollte sich den ObjC-Wrapper von Mike Ash für die Objective-C-Laufzeit ansehen .

So würden Sie mehr oder weniger vorgehen:

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

Von dort aus ist es einfach, die tatsächlichen Werte der Eigenschaften einer Instanz abzurufen, aber Sie müssen überprüfen, ob es sich um primitive Typen oder Objekte handelt. Daher war ich zu faul, um sie einzufügen. Sie können auch die Vererbungskette scannen Ruft alle für ein Objekt definierten Eigenschaften ab. Dann gibt es Methoden, die für Kategorien definiert sind, und mehr ... Aber fast alles ist leicht verfügbar.

Hier ist ein Auszug dessen, was der obige Code für UILabel ausgibt:

{
    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
    );
}
Felixyz
quelle
6
+1 so würde ich es machen (mache es zu einer NSObjectKategorie). Sie müssen jedoch free()Ihre ivars, propertiesund methodsArrays, oder Sie lecken sie.
Dave DeLong
Woops, habe das vergessen. Setzen Sie sie jetzt ein. Vielen Dank.
Felixyz
4
Können wir die Werte der Ivare nicht sehen?
Samhan Salahuddin
2
... und ihre Typen. Mir ist aufgefallen, dass Sie gesagt haben, es könnte getan werden, aber Sie scheinen dies definitiv besser zu verstehen als ich. Könnten Sie dies irgendwann mit dem Code aktualisieren, um die Werte und Typen für die ivars und Eigenschaften zu erhalten?
GeneralMike
Ist es bei ARC weiterhin erforderlich, Ivars, Eigenschaften und Methodenarrays usw. freizugeben?
Chuck Krutsinger
12

descriptionOhne die Methode (wie .toString () in Java) habe ich noch nichts von einer eingebauten Methode gehört, aber es wäre nicht allzu schwierig, eine zu erstellen. Die Objective-C-Laufzeitreferenz enthält eine Reihe von Funktionen, mit denen Sie Informationen zu Instanzvariablen, Methoden, Eigenschaften usw. eines Objekts abrufen können.

Dave DeLong
quelle
+1 für die Information, genau das, wonach ich gesucht habe. Trotzdem hoffte ich auf eine vorgefertigte Lösung, da ich neu genug in der Sprache bin, dass alles, was ich schnell zusammen hacken würde, einige wichtige Feinheiten der Sprache übersehen könnte.
Alan Storm
7
Wenn Sie eine Antwort ablehnen möchten, haben Sie bitte die Höflichkeit, einen Kommentar zu hinterlassen, warum.
Dave DeLong
8

Folgendes wird derzeit verwendet, um Klassenvariablen automatisch in einer Bibliothek für eine spätere öffentliche Veröffentlichung zu drucken. Dabei werden alle Eigenschaften der Instanzklasse bis zum Ende des Vererbungsbaums gesichert. Dank KVC müssen Sie sich nicht darum kümmern, ob eine Eigenschaft ein primitiver Typ ist oder nicht (für die meisten Typen).

// 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]]];
}
Kendall Helmstetter Gelner
quelle
+1 sehr nützlich, obwohl es die Frage nur teilweise beantwortet. Fügen Sie hier einen Kommentar hinzu, wenn Sie die Bibliothek freigeben?
Felixyz
@KendallHelmstetterGelner - GROSSE INFO !! Haben Sie eine Idee, wie Sie die tatsächliche Eigenschaft festlegen können, wenn Sie sie mithilfe von Reflection gefunden haben? Ich habe hier eine Frage: stackoverflow.com/q/25538890/1735836
Patricia
@Lucy, alle Zeichenfolgen, die Sie mit dieser Technik zurückerhalten, können in [myObject setValue: myValue forKey: propertyName] verwendet werden. Die Technik, die ich oben beschrieben habe (unter Verwendung der Eigenschaftsliste), ist Reflexion ... Sie können auch das Array objc_property_t verwenden, um die Eigenschaftsmethoden direkt aufzurufen, aber setValue: forKey: ist einfacher zu verwenden und funktioniert genauso gut.
Kendall Helmstetter Gelner
4

Ich habe ein paar Änderungen an Kendalls Code zum Drucken von Eigenschaftswerten vorgenommen, was für mich sehr praktisch war. Ich habe es als Instanzmethode anstelle einer Klassenmethode definiert, wie es die Superklassenrekursion so nennt. Ich habe auch die Ausnahmebehandlung für nicht KVO-kompatible Eigenschaften hinzugefügt und der Ausgabe Zeilenumbrüche hinzugefügt, um das Lesen (und Diff) zu vereinfachen:

-(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;
}
Christopher Pickslay
quelle
TOLLE INFO !!! - Haben Sie eine Idee, wie Sie die tatsächliche Eigenschaft festlegen können, wenn Sie sie mithilfe von Reflection gefunden haben? Ich habe hier eine Frage: stackoverflow.com/q/25538890/1735836
Patricia
3

Ehrlich gesagt ist das richtige Tool für diesen Job der Debugger von Xcode. All diese Informationen sind auf visuelle Weise leicht zugänglich. Nehmen Sie sich Zeit, um zu lernen, wie man es benutzt. Es ist ein wirklich mächtiges Werkzeug.

Mehr Informationen:

Verwenden des Debuggers

Veraltetes Xcode-Debugging-Handbuch - von Apple archiviert

Informationen zum Debuggen mit Xcode - von Apple archiviert

Über LLDB und Debugging - archiviert von Apple

Debuggen mit GDB - von Apple archiviert

SpriteKit Debugging Guide - von Apple archiviert

Debuggen von Programmierthemen für Core Foundation - archiviert von Apple

Colin Barrett
quelle
4
+1 für gute Informationen, aber manchmal ist es schneller, den Status eines Objekts an zwei Punkten zu sichern und die Ausgabe zu differenzieren, als zu einem bestimmten Zeitpunkt mit dem Status eines Programms zu beginnen.
Alan Storm
1

Ich habe daraus einen Cocoapod gemacht, https://github.com/neoneye/autodescribe

Ich habe den Code von Christopher Pickslay geändert und ihn zu einer Kategorie in NSObject gemacht und ihm auch eine Unittest hinzugefügt. So verwenden Sie es:

@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
neoneye
quelle
0

Ich bin vorher mit Introspektion und Refektion verwechselt, also holen Sie sich unten einige Informationen.

Introspektion ist die Fähigkeit des Objekts, zu überprüfen, um welchen Typ es sich handelt, welches Protokoll es entspricht oder welchen Selektor es beantworten kann. Die objc api wie isKindOfClass/ isMemberOfClass/ conformsToProtocol/ respondsToSelectoretc.

Die Refektionsfähigkeit ist weiter als die Introspektion. Sie kann nicht nur Objektinformationen abrufen, sondern auch Objektmetadaten, -eigenschaften und -funktionen bedienen. wie object_setClasskann Objekttyp ändern.

Jone
quelle