Отримайте список властивостей об'єкта в Objective-C


109

Як я можу отримати список (у вигляді NSArrayабо NSDictionary) заданих властивостей об'єкта в Objective-C?

Уявіть наступний сценарій: я визначив батьківський клас, який просто розширює NSObject, який містить властивості ,, NSStringa BOOLта NSDataоб'єкт як властивості. Тоді у мене є кілька класів, які розширюють цей батьківський клас, додаючи багато різних властивостей кожен.

Чи я можу реалізувати метод екземпляра для батьківського класу, який проходить через весь об'єкт і повертає, скажімо, NSArrayкожен із властивостей (дочірнього) класу як таких, NSStringsщо не є батьківським класом, тож я можу згодом використовувати ці NSStringдля КВК?

Відповіді:


116

Я просто зумів отримати відповідь сам. Використовуючи бібліотеку виконання програми Obj-C, я отримав доступ до властивостей так, як я хотів:

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

Це вимагало від мене функції "getPropertyType" C, яка в основному взята з зразка коду Apple (зараз не можу згадати точне джерело):

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 "@";
}

5
+1, крім цього, призведе до помилки на примітивах, таких як int. Будь ласка, дивіться мою відповідь нижче щодо трохи вдосконаленої версії цього самого.
jpswain

1
З-за правильності, [NSString stringWithCString:]це застаріло на користь [NSString stringWithCString:encoding:].
zekel

4
Якщо імпортувати заголовок виконання objc #import <objc / runtime.h> Він працює на ARC.
Dae KIM

Ось як це зробити за допомогою Swift.
Раміс

76

@ відповідь boliva хороша, але для обробки примітивів, таких як int, long, float, double і т.д., потрібно трохи додатково.

Я створив його, щоб додати цю функціональність.

// 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

1
Ви мали намір #import <Foundation/Foundation.h>у верхній частині файлу .h?
Андрій

2
[NSString stringWithUTF8String: propType] не зміг проаналізувати "propType const char *" NSNumber \ x94 \ xfdk; "і поверне нульову рядок ... Не знаю, чому це такий дивний NSNumber. Мб, тому що ActiveRecord?
Dumoko

Чудово! Дуже дякую.
Азік Абдулла

Це абсолютно ідеально!
Праной C

28

У відповіді @ orange80 є одна проблема: вона насправді не завжди закінчує рядок з 0. Це може призвести до несподіваних результатів, таких як збої під час спроби перетворити його на UTF8 (я насправді мав досить дратівливу аварійну помилку саме через це. Було весело налагоджувати його ^^). Я виправив це, фактично отримавши NSString з атрибута, а потім викликав cStringUsingEncoding:. Це зараз працює як шарм. (Також працює з ARC, принаймні для мене)

Отже, це моя версія коду зараз:

// 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

@farthen Ви можете навести приклад, який демонструє проблему з наданим мені кодом? Мені просто цікаво це побачити.
jpswain

@ orange80 Ну, AFAIR дані взагалі ніколи не припиняються. Якщо це так, це відбувається лише випадково. Я, можливо, помиляюся. Інші новини: У мене все ще працює цей код, і він працює рок-солід: p
felinira

@ orange80 Я зіткнувся з цією проблемою, намагаючись викликати вашу версію в IMAAdRequest з бібліотеки оголошень IMA google. рішення Фартена вирішило це.
Крістофер Пікслі

Дякую. Це працювало для мене в iOS7, коли попередні два відповіді не відповідали. +1 для всіх 3.
ChrisH

Це єдина відповідь, яка працювала на мене. Все інше надавало мені на кшталт "NSString \ x8d \ xc0 \ xd9" дивацтва для типів власності, імовірно, тому, що розмір "char *" був вимкнений
Брайан Колавіто,

8

Коли я спробував з iOS 3.2, функція getPropertyType не добре працює з описом властивості. Я знайшов приклад із документації на iOS: "Посібник з програмування Objective-C: Оголошені властивості".

Ось переглянений код списку властивостей у 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);

7

Я виявив, що розчин боліви відмінно працює в тренажері, але на пристрої підстрока фіксованої довжини викликає проблеми. Я написав більш об'єктивне для цієї проблеми рішення, яке працює на пристрої. У своїй версії я перетворюю C-String атрибутів у NSString і виконую на ньому рядкові операції, щоб отримати підрядку лише опису типу.

/*
 * @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;
}

Цей викид EXC_BAD_ACCESS виняток для NSRange const typeRangeStart = [атрибути rangeOfString: @ "T @ \" "]; // початок рядка типу
Адам Мендоса

6

Ця реалізація працює як з типом об'єктів Objective-C, так і з примітивами C. Це iOS 8 сумісний. Цей клас пропонує три методи класу:

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

Повертає словник усіх видимих ​​властивостей об’єкта, включаючи ті, що є у всіх його суперкласах.

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

Повертає словник усіх видимих ​​властивостей класу, включаючи ті, що є у всіх його суперкласах.

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

Повертає словник усіх видимих ​​властивостей, характерних для підкласу. Властивості його суперкласів не включаються.

Один корисний приклад використання цих методів - це копіювання об'єкта в екземпляр підкласу в Objective-C, не вказуючи властивості методу копіювання. . Частини цієї відповіді базуються на інших відповідях на це питання, але це забезпечує більш чистий інтерфейс до потрібної функції.

Заголовок:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

Впровадження:

//  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

Я отримую EXC_BAD_ACCESS виняток у цьому рядку NSString * name = [[NSString alloc] initWithBytes: атрибут + 1 довжина: strlen (атрибут) - 1 кодування: NSASCIIStringEncoding];
Адам Мендоса

4

Якщо хтось потребує отримання властивостей, успадкованих від батьківських класів (як я це робив), ось деяка модифікація коду " orange80 ", щоб зробити його рекурсивним:

+ (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];

    }
}

1
Чи не можемо ми зробити цю категорію і розширити NSObject за допомогою неї, щоб ця функціональність вбудована у кожен клас, який є дочірньою NSObject?
Олексій Заватоне

Це звучить як гарна ідея, якщо я знайду час, я оновлю відповідь з цим варіантом.
PakitoV

Після того, як ви закінчите це, я додаю дамп методу, коли матиму час. Настав час, коли ми отримаємо реальну властивість об'єкта та інтроспекцію методу у верхній частині кожного NSObject.
Олексій Заватоне

Я також працював над додаванням вартості виводу, але виявляється, що для деяких структур (rects) тип є фактичним значенням властивості. Це стосується caretRect таблиціViewController та інших неподписаних входів у структурі viewController, повертаючи c або f як тип, що суперечить об'єктивним документам C-Runtime. Очевидно, що для завершення цього потрібно більше роботи. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone

Я розглядав, але є проблема, яку я не можу вирішити, щоб зробити її рекурсивною, мені потрібно викликати метод для суперкласу (як в останньому рядку попереднього коду), оскільки NSObject - це кореневий клас, який не буде працювати всередині категорії . Тож ніяка рекурсивність неможлива ... :( не впевнений, що категорія в NSObject - це шлях для
перебігу

3

Слово "атрибути" трохи нечітке. Ви маєте на увазі змінні екземпляри, властивості, методи, схожі на аксесуари?

Відповідь на всі три - «так, але це не дуже просто». Objective-C у час виконання API , включає в себе функцію , щоб отримати список ІВАР, список методів або список властивостей для класу (наприклад, class_copyPropertyList()), а потім відповідна функція для кожного типу , щоб отримати ім'я елемента в списку (наприклад, property_getName()).

Загалом, це може зробити багато роботи, щоб виправити це, або, принаймні, набагато більше, ніж більшість людей хотіли б зробити для того, що зазвичай є дійсно тривіальною особливістю.

Крім того, ви можете просто написати сценарій Ruby / Python, який просто зчитує файл заголовка і шукає все, що б ви вважали "атрибутами" для класу.


Привіт чак, дякую за вашу відповідь. Те, що я посилався на "атрибути", було справді властивостями класу. Мені вже вдалося здійснити те, що я хотів, використовуючи бібліотеку виконання програми Obj-C. Використання сценарію для розбору файлу заголовка не працювало б для того, що мені потрібно під час виконання.
боліва

3

Мені вдалося отримати відповідь @ orange80 для роботи з ARC ENABLED ... ... для того, що я хотів - принаймні ... але не без трохи спроб і помилок. Сподіваємось, ця додаткова інформація може пошкодити комусь горе.

Збережіть ті класи, які він описує у своїй відповіді = як клас, і у своєму AppDelegate.h(або будь-якому іншому), поставленому #import PropertyUtil.h. Тоді у вашому ...

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

метод (або будь-який інший)

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

Секрет полягає в передачі змінної екземпляра вашого класу ( в цьому випадку мій клас є Gist, і мій екземпляр Gistєgist ), що ви хочете запитувати ... до NSObject ... (id)і т. Д. , Не вирізати .. для різних, дивних , езотеричні причини. Це дасть вам такий вихід…

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

Незважаючи на те, що Apple вихваляється від ODC "Amazeballs" "самоаналіз ... Вони впевнені, що це не дуже просто виконати цей простий" погляд "на себе", "так би мовити" ..

Якщо ви дійсно хочете піти на диких коней .. перевіряйте .. клас-дамп , що є розумно божевільним способом зазирнути до заголовків класів будь-яких виконуваних файлів тощо… Це забезпечує ВЕРБОЗА заглянути у ваші класи… що я, особисто знайдіть справді корисну - за багатьох, багатьох обставин. це насправді, тому я почав шукати рішення питання про ОП. ось деякі параметри використання .. насолоджуйтесь!

    -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

3

У вас є три магічні заклинання

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.

Наступний фрагмент коду може вам допомогти.

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

2

Я використовував функцію boliva за умови, але, мабуть, вона перестала працювати з iOS 7. Тож тепер замість статичного const char * getPropertyType (objc_property_t властивість) можна просто використовувати наступне:

- (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:@""];
}

Ти мій герой. Я все ще повинен вручну виправити деякі речі (чомусь BOOL підходять як "Tc"), але це фактично дозволило мені знову працювати.
Harpastum

Примітиви мають власний тип, "@" позначає об'єкти, а після нього назва ланки з'являється між лапками. Єдиний виняток - ідентифікатор, який кодується просто як "T @"
Mihai Timar

2

Для швидких відвідувачів Swift ви можете отримати цю функцію, скориставшись Encodableфункціоналом. Я поясню, як:

  1. Підтвердьте свій об'єкт до Encodableпротоколу

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Створіть розширення для Encodableзабезпечення toDictionaryфункціональності

     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. Зателефонуйте toDictionaryна свій примірник об'єкта та отримайте доступ до keysвласності.

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Вуаля! Отримайте доступ до своїх властивостей так:

    for k in exampleObj!.keys {
        print(k)
    }
    // Prints "prop1"
    // Prints "prop2"

1

Ці відповіді корисні, але я вимагаю від цього більше. Все, що я хочу зробити, - це перевірити, чи клас класу властивості рівний класу існуючого об'єкта. Усі наведені вище коди не здатні це робити, оскільки: Щоб отримати назву класу об'єкта, object_getClassName () повертає такі тексти:

__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:])

Але якщо виклик getPropertyType (...) зверху зразкового коду, дотеп 4 objc_property_t структурує властивості класу, визначені так:

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

він повертає рядки відповідно наступним чином:

NSArray
NSArray
NSNumber
NSValue

Тому він не в змозі визначити, чи здатний NSObject бути значенням одного властивості класу. Як це зробити тоді?

Ось мій повний зразок коду (функція getPropertyType (...) така сама, як і вище):

#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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.