Introspekcja / refleksja z celem-C

79

Czy istnieje wbudowana metoda, funkcja, interfejs API, powszechnie przyjęty sposób itp. W celu zrzucenia zawartości instancji obiektu w Objective-C, szczególnie w środowisku Apple Cocoa / Cocoa-Touch?

Chcę móc zrobić coś takiego

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

i wyświetlają nazwy i wartości zmiennych instancji obiektu, wraz z metodami dostępnymi do wywołania w czasie wykonywania. Idealnie w czytelnym formacie.

Dla programistów zaznajomionych z PHP, zasadniczo szukam odpowiednika funkcji odbicia ( var_dump(), get_class_methods()) i API OO Reflection.

Alan Storm
źródło
Zastanawiam się, że to samo pytanie jest kilka dni. Dziękuję za to pytanie.
Yannick Loriot
7
Tak, świetne pytanie. Jedną z największych zalet ObjC w porównaniu z innymi podobnymi językami jest niesamowity dynamiczny system wykonawczy, który pozwala robić takie niesamowite rzeczy. Niestety, ludzie rzadko wykorzystują go w pełni, więc chwała za nauczenie społeczności SO o tym za pomocą twojego pytania.
rpj
Stworzyłem lekką bibliotekę do radzenia sobie z refleksjami OSReflectionKit . Korzystając z tej biblioteki, możesz po prostu wywołać [the_thing fullDescription].
Alexandre OS
WIELKIE Pytanie !! - Czy masz pomysł, jak ustawić rzeczywistą właściwość po znalezieniu jej za pomocą Reflection? Mam tutaj pytanie: stackoverflow.com/q/25538890/1735836
Patricia

Odpowiedzi:

112

AKTUALIZACJA: Każdy, kto chce robić tego typu rzeczy, może chcieć sprawdzić opakowanie ObjC Mike'a Asha dla środowiska wykonawczego Objective-C .

Mniej więcej tak możesz to zrobić:

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

Stamtąd łatwo jest uzyskać rzeczywiste wartości właściwości instancji, ale musisz sprawdzić, czy są to typy prymitywne lub obiekty, więc byłem zbyt leniwy, aby je wprowadzić. Możesz także przeskanować łańcuch dziedziczenia, aby pobierz wszystkie właściwości zdefiniowane w obiekcie. Są też metody zdefiniowane na kategoriach i nie tylko ... Ale prawie wszystko jest łatwo dostępne.

Oto fragment tego, co zrzuca powyższy kod dla 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
    );
}
Felixyz
źródło
6
+1, tak bym to zrobił (uczyniłbym to NSObjectkategorią). Jednak trzeba się free()swoimi ivars, propertiesi methodstablic, albo jesteś ich przecieka.
Dave DeLong,
Ups, zapomniałem o tym. Włóż je teraz. Dzięki.
Felixyz,
4
Czy nie widzimy wartości ivars?
Samhan Salahuddin,
2
... i ich typy. Zauważyłem, że powiedziałeś, że da się to zrobić, ale zdecydowanie lepiej to rozumiesz niż ja. Czy byłbyś w stanie zaktualizować to w pewnym momencie za pomocą kodu, aby uzyskać wartości i typy dla ivars i właściwości?
GeneralMike,
Czy w przypadku ARC nadal konieczne jest zwalnianie blach, tablic właściwości i metod itp.?
Chuck Krutsinger,
12

Krótki z descriptionmetodą (jak .ToString () w Javie), nie słyszałem od jednego, który został zbudowany w, ale to nie byłoby zbyt trudne, aby je utworzyć. Dokumentacja Objective-C Runtime zawiera szereg funkcji, których można użyć do uzyskania informacji o zmiennych instancji obiektu, metodach, właściwościach itp.

Dave DeLong
źródło
+1 dla informacji, właśnie tego, czego szukałem. To powiedziawszy, liczyłem na gotowe rozwiązanie, ponieważ jestem na tyle nowy w języku, że wszystko, co szybko zhakuję, może przegapić niektóre główne elementy języka.
Alan Storm,
7
Jeśli zamierzasz zlekceważyć odpowiedź, uprzejmie zostaw komentarz dlaczego.
Dave DeLong,
8

Oto, czego obecnie używam do automatycznego drukowania zmiennych klas w bibliotece do ostatecznego publicznego wydania - działa poprzez zrzucanie wszystkich właściwości z klasy instancji do kopii zapasowej drzewa dziedziczenia. Dzięki KVC nie musisz się martwić, czy właściwość jest typem pierwotnym, czy nie (dla większości typów).

// 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
źródło
+1 bardzo przydatne, chociaż odpowiada na pytanie tylko częściowo. Czy dodasz tutaj komentarz po wydaniu biblioteki?
Felixyz,
@KendallHelmstetterGelner - WIELKA INFO !! Czy masz pomysł, jak ustawić rzeczywistą właściwość po znalezieniu jej za pomocą Reflection? Mam tutaj pytanie: stackoverflow.com/q/25538890/1735836
Patricia
@Lucy, każdy z ciągów odzyskanych przy użyciu tej techniki może być użyty w [myObject setValue: myValue forKey: propertyName]. Technika, którą opisałem powyżej (używając listy właściwości) jest odbiciem ... możesz również użyć tablicy objc_property_t do bezpośredniego wywołania metod właściwości, ale setValue: forKey: jest łatwiejszy w użyciu i działa równie dobrze.
Kendall Helmstetter Gelner
4

Zrobiłem kilka poprawek w kodzie Kendall do drukowania wartości właściwości, co bardzo mi się przydało. Zdefiniowałem to jako metodę instancji zamiast metody klasowej, ponieważ tak nazywa ją rekursja nadklasy. Dodałem również obsługę wyjątków dla właściwości niezgodnych z KVO i dodałem podziały wierszy do wyjścia, aby ułatwić czytanie (i porównywanie):

-(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
źródło
WIELKA INFO !!! - Czy masz pomysł, jak ustawić rzeczywistą właściwość po znalezieniu jej za pomocą Reflection? Mam tutaj pytanie: stackoverflow.com/q/25538890/1735836
Patricia
3

Szczerze mówiąc, odpowiednim narzędziem do tego zadania jest debugger Xcode. Wszystkie te informacje są łatwo dostępne w wizualny sposób. Poświęć trochę czasu, aby nauczyć się go używać, to naprawdę potężne narzędzie.

Więcej informacji:

Korzystanie z debugera

Nieaktualny przewodnik debugowania Xcode - zarchiwizowany przez Apple

Informacje o debugowaniu za pomocą Xcode - zarchiwizowane przez Apple

Informacje o LLDB i debugowaniu - zarchiwizowane przez Apple

Debugowanie za pomocą GDB - zarchiwizowane przez Apple

Przewodnik debugowania SpriteKit - zarchiwizowany przez Apple

Debugowanie tematów programowania dla Core Foundation - zarchiwizowane przez Apple

Colin Barrett
źródło
4
+1 dla dobrych informacji, ale czasami szybciej jest zrzucić stan obiektu w dwóch punktach i porównać dane wyjściowe, niż zaczyna się w stanie programu w pojedynczym punkcie w czasie.
Alan Storm
1

Zrobiłem z tego cocoapod, https://github.com/neoneye/autodescribe

Zmodyfikowałem kod Christophera Pickslaya i uczyniłem go kategorią na NSObject, a także dodałem do niego unittest. Oto jak z niego korzystać:

@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
źródło
0

Wcześniej jestem mylony z introspekcją i refleksją, więc uzyskaj poniżej kilka informacji.

Introspekcja to zdolność obiektu do sprawdzenia, jakiego typu jest, protokołu, który jest zgodny, lub selektora, na który może odpowiedzieć. Interfejs API objc, taki jak isKindOfClass/ isMemberOfClass/ conformsToProtocol/ respondsToSelectoritp.

Zdolność do refleksji jest czymś więcej niż introspekcja , Nie tylko może uzyskać informacje o obiekcie, ale także może obsługiwać metadane, właściwości i funkcje obiektu. takie jak object_setClassmogą modyfikować typ obiektu.

Jone
źródło