Pobierz listę właściwości obiektu w Objective-C

109

Jak mogę uzyskać listę (w postaci NSArraylub NSDictionary) właściwości danego obiektu w Objective-C?

Wyobraź sobie następujący scenariusz: zdefiniowałem klasę nadrzędną, która po prostu rozszerza NSObject, która przechowuje an NSString, a BOOLi NSDataobiekt jako właściwości. Następnie mam kilka klas, które rozszerzają tę klasę nadrzędną, dodając do każdej z nich wiele różnych właściwości.

Czy istnieje sposób, w jaki mógłbym zaimplementować metodę instancji w klasie nadrzędnej, która przechodzi przez cały obiekt i zwraca, powiedzmy, NSArraykażdą z właściwości klasy (potomnej), ponieważ nieNSStrings ma ich w klasie nadrzędnej, więc mogę później ich użyć NSStringdla KVC?

boliva
źródło

Odpowiedzi:

116

Po prostu udało mi się sam uzyskać odpowiedź. Korzystając z biblioteki wykonawczej Obj-C, miałem dostęp do właściwości tak, jak chciałem:

- (void)myMethod {
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithCString:propName
                                                                encoding:[NSString defaultCStringEncoding]];
            NSString *propertyType = [NSString stringWithCString:propType
                                                                encoding:[NSString defaultCStringEncoding]];
            ...
        }
    }
    free(properties);
}

Wymagało to ode mnie utworzenia funkcji C „getPropertyType”, która jest głównie pobierana z przykładowego kodu Apple (nie pamiętam teraz dokładnego źródła):

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T') {
            if (strlen(attribute) <= 4) {
                break;
            }
            return (const char *)[[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
        }
    }
    return "@";
}
boliva
źródło
5
+1 z wyjątkiem tego, że spowoduje to błąd w prymitywach, takich jak int. Proszę zobaczyć moją odpowiedź poniżej dla nieco ulepszonej wersji tego samego.
jpswain
1
Ze względu na poprawność [NSString stringWithCString:]jest przestarzały na korzyść [NSString stringWithCString:encoding:].
zekel
4
Należy zaimportować nagłówek środowiska wykonawczego objc #import <objc / runtime.h> Działa na ARC.
Dae KIM
Oto, jak można to osiągnąć za pomocą Swift.
Ramis,
76

Odpowiedź @ boliva jest dobra, ale wymaga trochę więcej, aby obsługiwać prymitywy, takie jak int, long, float, double itp.

Zbudowałem go, aby dodać tę funkcjonalność.

// PropertyUtil.h
#import 

@interface PropertyUtil : NSObject

+ (NSDictionary *)classPropsFor:(Class)klass;

@end


// PropertyUtil.m
#import "PropertyUtil.h"
#import "objc/runtime.h"

@implementation PropertyUtil

static const char * getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:
            /* 
                if you want a list of what will be returned for these primitives, search online for
                "objective-c" "Property Attribute Description Examples"
                apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.            
            */
            return (const char *)[[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes];
        }        
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            return (const char *)[[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];
        }
    }
    return "";
}


+ (NSDictionary *)classPropsFor:(Class)klass
{    
    if (klass == NULL) {
        return nil;
    }

    NSMutableDictionary *results = [[[NSMutableDictionary alloc] init] autorelease];

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [results setObject:propertyType forKey:propertyName];
        }
    }
    free(properties);

    // returning a copy here to make sure the dictionary is immutable
    return [NSDictionary dictionaryWithDictionary:results];
}




@end
jpswain
źródło
1
Czy zamierzałeś mieć #import <Foundation/Foundation.h>na początku pliku .h?
Andrew,
2
[NSString stringWithUTF8String: propType] nie mógł przeanalizować "propType const char *" NSNumber \ x94 \ xfdk; "i zwraca ciąg zerowy ... Nie wiem, dlaczego jest to taki dziwny NSNumber. Mb, ponieważ ActiveRecord?
Dumoko
Wspaniały! Wielkie dzięki.
Azik Abdullah
To jest absolutnie doskonałe!
Pranoy C
28

Odpowiedź @ orange80 ma jeden problem: w rzeczywistości nie zawsze kończy ciąg z 0. Może to prowadzić do nieoczekiwanych rezultatów, takich jak awaria podczas próby konwersji do UTF8 (z tego powodu miałem dość denerwującego crashbuga. Fajnie było go debugować ^^). Naprawiłem to, pobierając NSString z atrybutu, a następnie wywołując cStringUsingEncoding :. To działa teraz jak urok. (Działa również z ARC, przynajmniej dla mnie)

Więc to jest teraz moja wersja kodu:

// PropertyUtil.h
#import 

@interface PropertyUtil : NSObject

+ (NSDictionary *)classPropsFor:(Class)klass;

@end


// PropertyUtil.m
#import "PropertyUtil.h"
#import <objc/runtime.h>

@implementation PropertyUtil

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    //printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:
            /*
             if you want a list of what will be returned for these primitives, search online for
             "objective-c" "Property Attribute Description Examples"
             apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.
             */
            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}


+ (NSDictionary *)classPropsFor:(Class)klass
{
    if (klass == NULL) {
        return nil;
    }

    NSMutableDictionary *results = [[NSMutableDictionary alloc] init];

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [results setObject:propertyType forKey:propertyName];
        }
    }
    free(properties);

    // returning a copy here to make sure the dictionary is immutable
    return [NSDictionary dictionaryWithDictionary:results];
}

@end
felinira
źródło
@farthen czy możesz podać przykład ilustrujący problem z podanym przeze mnie kodem? jestem po prostu ciekawy, jak to zobaczyć.
jpswain
@ orange80 Cóż, AFAIR dane nigdy nie są w ogóle zakończone zerem. Jeśli tak jest, dzieje się to tylko przypadkowo. Chociaż mogę się mylić. W innych wiadomościach: nadal mam uruchomiony kod i działa solidnie: p
felinira
@ orange80 Napotkałem ten problem, próbując wywołać Twoją wersję na IMAAdRequest z biblioteki reklam Google IMA. Rozwiązanie Farthena rozwiązało problem.
Christopher Pickslay
Dzięki. To działało dla mnie w iOS7, gdy poprzednie dwie odpowiedzi nie. +1 dla wszystkich 3.
ChrisH
To jedyna odpowiedź, która mi pomogła. Wszystko inne dawało mi dziwaczność typu „NSString \ x8d \ xc0 \ xd9” dla typów nieruchomości, prawdopodobnie dlatego, że rozmiar char * był niewłaściwy
Brian Colavito
8

Kiedy próbowałem z iOS 3.2, funkcja getPropertyType nie działa dobrze z opisem właściwości. Znalazłem przykład z dokumentacji iOS: „Przewodnik programowania w środowisku wykonawczym Objective-C: deklarowane właściwości”.

Oto poprawiony kod listy nieruchomości w iOS 3.2:

#import <objc/runtime.h>
#import <Foundation/Foundation.h>
...
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([UITouch class], &outCount);
for(i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
free(properties);
Chatchavan
źródło
7

Odkryłem, że rozwiązanie bolivy działa dobrze w symulatorze, ale na urządzeniu podciąg o stałej długości powoduje problemy. Napisałem bardziej przyjazne dla Objective-C rozwiązanie tego problemu, które działa na urządzeniu. W mojej wersji konwertuję C-String atrybutów na NSString i wykonuję na nim operacje na łańcuchach, aby uzyskać podłańcuch zawierający tylko opis typu.

/*
 * @returns A string describing the type of the property
*/

+ (NSString *)propertyTypeStringOfProperty:(objc_property_t) property {
    const char *attr = property_getAttributes(property);
    NSString *const attributes = [NSString stringWithCString:attr encoding:NSUTF8StringEncoding];

    NSRange const typeRangeStart = [attributes rangeOfString:@"T@\""];  // start of type string
    if (typeRangeStart.location != NSNotFound) {
        NSString *const typeStringWithQuote = [attributes substringFromIndex:typeRangeStart.location + typeRangeStart.length];
        NSRange const typeRangeEnd = [typeStringWithQuote rangeOfString:@"\""]; // end of type string
        if (typeRangeEnd.location != NSNotFound) {
            NSString *const typeString = [typeStringWithQuote substringToIndex:typeRangeEnd.location];
            return typeString;
        }
    }
    return nil;
}

/**
* @returns (NSString) Dictionary of property name --> type
*/

+ (NSDictionary *)propertyTypeDictionaryOfClass:(Class)klass {
    NSMutableDictionary *propertyMap = [NSMutableDictionary dictionary];
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(klass, &outCount);
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {

            NSString *propertyName = [NSString stringWithCString:propName encoding:NSUTF8StringEncoding];
            NSString *propertyType = [self propertyTypeStringOfProperty:property];
            [propertyMap setValue:propertyType forKey:propertyName];
        }
    }
    free(properties);
    return propertyMap;
}
Mitchell Vanderhoeff
źródło
To zgłasza wyjątek EXC_BAD_ACCESS na NSRange const typeRangeStart = [atrybuty rangeOfString: @ "T @ \" "]; // początek typu string
Adam Mendoza
6

Ta implementacja działa zarówno z typami obiektów Objective-C, jak i prymitywami C. Jest kompatybilny z iOS 8. Ta klasa udostępnia trzy metody klas:

+ (NSDictionary *) propertiesOfObject:(id)object;

Zwraca słownik wszystkich widocznych właściwości obiektu, w tym właściwości wszystkich jego nadklas.

+ (NSDictionary *) propertiesOfClass:(Class)class;

Zwraca słownik wszystkich widocznych właściwości klasy, w tym właściwości wszystkich jej nadklas.

+ (NSDictionary *) propertiesOfSubclass:(Class)class;

Zwraca słownik wszystkich widocznych właściwości, które są specyficzne dla podklasy. Właściwości jego nadklas nie są uwzględnione.

Jednym z przydatnych przykładów użycia tych metod jest skopiowanie obiektu do instancji podklasy w Objective-C bez konieczności określania właściwości w metodzie kopiowania . Część tej odpowiedzi opiera się na innych odpowiedziach na to pytanie, ale zapewnia bardziej przejrzysty interfejs dla pożądanej funkcjonalności.

Nagłówek:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

@interface SYNUtilities : NSObject
+ (NSDictionary *) propertiesOfObject:(id)object;
+ (NSDictionary *) propertiesOfClass:(Class)class;
+ (NSDictionary *) propertiesOfSubclass:(Class)class;
@end

Realizacja:

//  SYNUtilities.m

#import "SYNUtilities.h"
#import <objc/objc-runtime.h>

@implementation SYNUtilities
+ (NSDictionary *) propertiesOfObject:(id)object
{
    Class class = [object class];
    return [self propertiesOfClass:class];
}

+ (NSDictionary *) propertiesOfClass:(Class)class
{
    NSMutableDictionary * properties = [NSMutableDictionary dictionary];
    [self propertiesForHierarchyOfClass:class onDictionary:properties];
    return [NSDictionary dictionaryWithDictionary:properties];
}

+ (NSDictionary *) propertiesOfSubclass:(Class)class
{
    if (class == NULL) {
        return nil;
    }

    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    return [self propertiesForSubclass:class onDictionary:properties];
}

+ (NSMutableDictionary *)propertiesForHierarchyOfClass:(Class)class onDictionary:(NSMutableDictionary *)properties
{
    if (class == NULL) {
        return nil;
    }

    if (class == [NSObject class]) {
        // On reaching the NSObject base class, return all properties collected.
        return properties;
    }

    // Collect properties from the current class.
    [self propertiesForSubclass:class onDictionary:properties];

    // Collect properties from the superclass.
    return [self propertiesForHierarchyOfClass:[class superclass] onDictionary:properties];
}

+ (NSMutableDictionary *) propertiesForSubclass:(Class)class onDictionary:(NSMutableDictionary *)properties
{
    unsigned int outCount, i;
    objc_property_t *objcProperties = class_copyPropertyList(class, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = objcProperties[i];
        const char *propName = property_getName(property);
        if(propName) {
            const char *propType = getPropertyType(property);
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            NSString *propertyType = [NSString stringWithUTF8String:propType];
            [properties setObject:propertyType forKey:propertyName];
        }
    }
    free(objcProperties);

    return properties;
}

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // A C primitive type:
            /*
             For example, int "i", long "l", unsigned "I", struct.
             Apple docs list plenty of examples of values returned. For a list
             of what will be returned for these primitives, search online for
             "Objective-c" "Property Attribute Description Examples"
             */
            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // An Objective C id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // Another Objective C id type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}

@end
Duncan Babbage
źródło
Otrzymuję wyjątek EXC_BAD_ACCESS w tej linii NSString * name = [[NSString assign] initWithBytes: atrybut + 1 długość: strlen (atrybut) - 1 kodowanie: NSASCIIStringEncoding];
Adam Mendoza
4

Jeśli ktoś potrzebuje uzyskać również właściwości odziedziczone po klasach nadrzędnych (tak jak ja), oto jakaś modyfikacja w kodzie " orange80 ", aby był rekurencyjny:

+ (NSDictionary *)classPropsForClassHierarchy:(Class)klass onDictionary:(NSMutableDictionary *)results
{
    if (klass == NULL) {
        return nil;
    }

    //stop if we reach the NSObject class as is the base class
    if (klass == [NSObject class]) {
        return [NSDictionary dictionaryWithDictionary:results];
    }
    else{

        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(klass, &outCount);
        for (i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            const char *propName = property_getName(property);
            if(propName) {
                const char *propType = getPropertyType(property);
                NSString *propertyName = [NSString stringWithUTF8String:propName];
                NSString *propertyType = [NSString stringWithUTF8String:propType];
                [results setObject:propertyType forKey:propertyName];
            }
        }
        free(properties);

        //go for the superclass
        return [PropertyUtil classPropsForClassHierarchy:[klass superclass] onDictionary:results];

    }
}
PakitoV
źródło
1
Czy nie moglibyśmy uczynić z tej kategorii kategorii i rozszerzyć o nią NSObject, aby ta funkcjonalność była wbudowana w każdą klasę, która jest dzieckiem NSObject?
Alex Zavatone
Brzmi to jak dobry pomysł, jeśli znajdę czas, zaktualizuję odpowiedź tą opcją.
PakitoV
Gdy skończysz, dodam zrzut metody, gdy będę miał czas. Najwyższy czas, abyśmy dodali do każdego obiektu NSO rzeczywistą właściwość obiektu i metodę introspekcji.
Alex Zavatone
Pracowałem również nad dodaniem wartości wyjściowej, ale wydaje się, że w przypadku niektórych struktur (prostokątów) typem jest rzeczywista wartość nieruchomości. Dzieje się tak w przypadku caretRect elementu tableViewController i innych wartości typu int bez znaku w strukturze viewController, które zwracają c lub f jako typ, który jest w konflikcie z dokumentacją Objective-C Runtime Docs. Oczywiście potrzeba więcej pracy, aby to zakończyć. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone,
Patrzyłem, ale jest problem, którego nie mogę obejść, aby uczynić go rekurencyjnym, muszę wywołać metodę dla nadklasy (jak w ostatnim wierszu poprzedniego kodu), ponieważ NSObject jest klasą główną, która nie będzie działać w kategorii . Więc żadna rekursywność nie jest możliwa ... :( nie jestem pewien, czy kategoria w NSObject jest już
właściwą
3

Słowo „atrybuty” jest trochę rozmyte. Masz na myśli zmienne instancji, właściwości, metody, które wyglądają jak akcesory?

Odpowiedź na wszystkie trzy pytania brzmi „tak, ale nie jest to łatwe”. Objective-C Runtime API zawiera funkcje, aby otrzymać listę Ivar, listy metoda lub listę właściwości dla klasy (na przykład class_copyPropertyList()), a następnie odpowiednią funkcję dla każdego typu, aby uzyskać nazwę elementu na liście (na przykład property_getName()).

Podsumowując, może to być trochę pracy, aby to zrobić dobrze, lub przynajmniej dużo więcej, niż większość ludzi chciałaby zrobić dla czegoś, co zwykle sprowadza się do naprawdę trywialnej funkcji.

Alternatywnie, możesz po prostu napisać skrypt Ruby / Python, który po prostu czyta plik nagłówkowy i szuka wszelkich „atrybutów” klasy.

Głaskanie pod brodę
źródło
Cześć Chuck, dzięki za twoją odpowiedź. To, o czym mówiłem, mówiąc o „atrybutach”, dotyczyło w istocie właściwości klasy. Udało mi się już osiągnąć to, co chciałem, korzystając z biblioteki wykonawczej Obj-C. Użycie skryptu do przeanalizowania pliku nagłówkowego nie zadziałałoby w przypadku tego, czego potrzebowałem w czasie wykonywania.
boliva
3

Udało mi się uzyskać odpowiedź @ orange80 dotyczącą pracy Z WŁĄCZONYM ŁUKIEM ... ... na to, czego chciałem - przynajmniej ... ale nie bez odrobiny prób i błędów. Miejmy nadzieję, że te dodatkowe informacje mogą oszczędzić komuś smutku.

Zapisz te klasy, które opisuje w swojej odpowiedzi = jako klasę, a w swoim AppDelegate.h(lub czymkolwiek) umieść #import PropertyUtil.h. Następnie w twoim ...

- (void)applicationDidFinishLaunching:
         (NSNotification *)aNotification {

metoda (lub cokolwiek)

PropertyUtil *props  = [PropertyUtil new];  
NSDictionary *propsD = [PropertyUtil classPropsFor:
                          (NSObject*)[gist class]];  
NSLog(@"%@, %@", props, propsD);

Sekretem jest rzutowanie zmiennej instancji twojej klasy ( w tym przypadku moja klasa to Gist, a moja instancja Gistjestgist ), którą chcesz odpytać ... do NSObject(id)itd., Nie przerwie tego ... dla różnych, dziwnych , ezoteryczne powody. To da ci takie wyniki…

<PropertyUtil: 0x7ff0ea92fd90>, {
apiURL = NSURL;
createdAt = NSDate;
files = NSArray;
gistDescription = NSString;
gistId = NSString;
gitPullURL = NSURL;
gitPushURL = NSURL;
htmlURL = NSURL;
isFork = c;
isPublic = c;
numberOfComments = Q;
updatedAt = NSDate;
userLogin = NSString;
}

Dla wszystkich nieskrępowanych / OCD Apple przechwalających się introspekcją ObjC „zdumiewające kulki”… Z pewnością nie ułatwiają wykonania tego prostego „spojrzenia” „na siebie”, „że tak powiem” ..

Jeśli jednak naprawdę chcesz zaszaleć… sprawdź… class-dump , który jest oszałamiająco szalonym sposobem na zajrzenie do nagłówków klas DOWOLNEGO pliku wykonywalnego itp.… Zapewnia VERBOSE spojrzenie na Twoje klasy… że ja, osobiście znaleźć naprawdę pomocne - w wielu, wielu okolicznościach. właśnie dlatego zacząłem szukać rozwiązania pytania PO. oto niektóre parametry użytkowe ... miłej zabawy!

    -a             show instance variable offsets
    -A             show implementation addresses
    --arch <arch>  choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64)
    -C <regex>     only display classes matching regular expression
    -f <str>       find string in method name
    -I             sort classes, categories, and protocols by inheritance (overrides -s)
    -r             recursively expand frameworks and fixed VM shared libraries
    -s             sort classes and categories by name
    -S             sort methods by name
Alex Gray
źródło
3

Masz trzy magiczne zaklęcia

Ivar* ivars = class_copyIvarList(clazz, &count); // to get all iVars
objc_property_t  *properties = class_copyPropertyList(clazz, &count); //to get all properties of a class 
Method* methods = class_copyMethodList(clazz, &count); // to get all methods of a class.

Poniższy fragment kodu może ci pomóc.

-(void) displayClassInfo
{
    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* classInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                           ivarArray, @"ivars",
                           propertyArray, @"properties",
                           methodArray, @"methods",
                           nil];

        NSLog(@"%@", classInfo);
}
Ans
źródło
2

Używałem dostarczonej funkcji boliva, ale najwyraźniej przestała działać z iOS 7. Więc teraz zamiast statycznej const char * getPropertyType (właściwość objc_property_t) można po prostu użyć następującego:

- (NSString*) classOfProperty:(NSString*)propName{

objc_property_t prop = class_getProperty([self class], [propName UTF8String]);
if (!prop) {
    // doesn't exist for object
    return nil;
}
const char * propAttr = property_getAttributes(prop);
NSString *propString = [NSString stringWithUTF8String:propAttr];
NSArray *attrArray = [propString componentsSeparatedByString:@","];
NSString *class=[attrArray objectAtIndex:0];
return [[class stringByReplacingOccurrencesOfString:@"\"" withString:@""] stringByReplacingOccurrencesOfString:@"T@" withString:@""];
}
Andrey Finayev
źródło
Jesteś moim bohaterem. Nadal muszę ręcznie poprawić niektóre rzeczy (z jakiegoś powodu BOOL-y pojawiają się jako „Tc”), ale to faktycznie pozwoliło mi znowu działać.
Harpastum
Prymitywy mają swój własny typ, „@” oznacza obiekty, a po nim nazwa klasy pojawia się w cudzysłowie. Jedynym wyjątkiem jest id, który jest zakodowany po prostu jako „T @”
Mihai Timar
2

Dla szybkich gapiów możesz uzyskać tę funkcjonalność, wykorzystując ją Encodable. Wytłumaczę jak:

  1. Zgodność obiektu z Encodableprotokołem

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Utwórz rozszerzenie dla, Encodableaby zapewnić toDictionaryfunkcjonalność

     public func toDictionary() -> [String: AnyObject]? {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data =  try? encoder.encode(self),
              let json = try? JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0)), let jsonDict = json as? [String: AnyObject] else {
            return nil
        }
        return jsonDict
    }
  3. Wywołaj toDictionaryinstancję obiektu i uzyskaj dostęp do keyswłaściwości.

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Voila! Uzyskaj dostęp do swoich nieruchomości w następujący sposób:

    for k in exampleObj!.keys {
        print(k)
    }
    // Prints "prop1"
    // Prints "prop2"
Harry Bloom
źródło
1

Te odpowiedzi są pomocne, ale wymagam od tego więcej. Wszystko, co chcę zrobić, to sprawdzić, czy typ klasy właściwości jest równy typowi istniejącego obiektu. Wszystkie powyższe kody nie są w stanie tego zrobić, ponieważ: Aby uzyskać nazwę klasy obiektu, object_getClassName () zwraca teksty takie jak te:

__NSArrayI (for an NSArray instance)
__NSArrayM (for an NSMutableArray instance)
__NSCFBoolean (an NSNumber object initialized by initWithBool:)
__NSCFNumber (an NSValue object initialized by [NSNumber initWithBool:])

Ale jeśli wywołujesz getPropertyType (...) z powyższego przykładowego kodu, z 4 objc_property_t strukturami właściwości klasy zdefiniowanej w następujący sposób:

@property (nonatomic, strong) NSArray* a0;
@property (nonatomic, strong) NSArray* a1;
@property (nonatomic, copy) NSNumber* n0;
@property (nonatomic, copy) NSValue* n1;

zwraca ciągi odpowiednio w następujący sposób:

NSArray
NSArray
NSNumber
NSValue

Nie jest więc w stanie określić, czy obiekt NSObject może być wartością jednej właściwości tej klasy. Jak więc to zrobić?

Oto mój pełny przykładowy kod (funkcja getPropertyType (...) jest taka sama jak powyżej):

#import <objc/runtime.h>

@interface FOO : NSObject

@property (nonatomic, strong) NSArray* a0;
@property (nonatomic, strong) NSArray* a1;
@property (nonatomic, copy) NSNumber* n0;
@property (nonatomic, copy) NSValue* n1;

@end

@implementation FOO

@synthesize a0;
@synthesize a1;
@synthesize n0;
@synthesize n1;

@end

static const char *getPropertyType(objc_property_t property) {
    const char *attributes = property_getAttributes(property);
    //printf("attributes=%s\n", attributes);
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        if (attribute[0] == 'T' && attribute[1] != '@') {
            // it's a C primitive type:

            // if you want a list of what will be returned for these primitives, search online for
            // "objective-c" "Property Attribute Description Examples"
            // apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc.

            NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
        else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
            // it's an ObjC id type:
            return "id";
        }
        else if (attribute[0] == 'T' && attribute[1] == '@') {
            // it's another ObjC object type:
            NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
            return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
        }
    }
    return "";
}

int main(int argc, char * argv[]) {
    NSArray* a0 = [[NSArray alloc] init];
    NSMutableArray* a1 = [[NSMutableArray alloc] init];
    NSNumber* n0 = [[NSNumber alloc] initWithBool:YES];
    NSValue* n1 = [[NSNumber alloc] initWithBool:NO];
    const char* type0 = object_getClassName(a0);
    const char* type1 = object_getClassName(a1);
    const char* type2 = object_getClassName(n0);
    const char* type3 = object_getClassName(n1);

    objc_property_t property0 = class_getProperty(FOO.class, "a0");
    objc_property_t property1 = class_getProperty(FOO.class, "a1");
    objc_property_t property2 = class_getProperty(FOO.class, "n0");
    objc_property_t property3 = class_getProperty(FOO.class, "n1");
    const char * memberthype0 = getPropertyType(property0);//property_getAttributes(property0);
    const char * memberthype1 = getPropertyType(property1);//property_getAttributes(property1);
    const char * memberthype2 = getPropertyType(property2);//property_getAttributes(property0);
    const char * memberthype3 = getPropertyType(property3);//property_getAttributes(property1);
    NSLog(@"%s", type0);
    NSLog(@"%s", type1);
    NSLog(@"%s", type2);
    NSLog(@"%s", type3);
    NSLog(@"%s", memberthype0);
    NSLog(@"%s", memberthype1);
    NSLog(@"%s", memberthype2);
    NSLog(@"%s", memberthype3);

    return 0;
}
godspeed1024
źródło