Получить список свойств объекта в Objective-C


109

Как я могу получить список (в форме NSArrayили NSDictionary) свойств данного объекта в Objective-C?

Представьте себе следующий сценарий: Я определил родительский класс , который только расширяет NSObject, что держит NSString, BOOLи в NSDataобъект как свойства. Затем у меня есть несколько классов, которые расширяют этот родительский класс, добавляя к каждому множество различных свойств.

Могу ли я реализовать метод экземпляра в родительском классе, который проходит через весь объект и возвращает, скажем, NSArrayкаждое из свойств (дочернего) класса, поскольку NSStringsони не находятся в родительском классе, поэтому я могу позже использовать их NSStringдля KVC?

Ответы:


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

Это потребовало от меня создания функции C getPropertyType, которая в основном взята из образца кода 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?
Думоко

Superb! Большое спасибо.
Азик Абдулла

Это абсолютно идеально!
Pranoy C

28

Ответ @ orange80 имеет одну проблему: на самом деле он не всегда завершает строку нулями. Это может привести к неожиданным результатам, таким как сбой при попытке преобразовать его в 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 07

@ orange80 Ну, AFAIR данные вообще никогда не заканчиваются нулем. Если это так, то это происходит только случайно. Хотя я могу ошибаться. Из других новостей: у меня все еще работает этот код, и он работает
безупречно

@ orange80 Я столкнулся с этой проблемой, пытаясь вызвать вашу версию в IMAAdRequest из рекламной библиотеки Google IMA. Решение Фарфена разрешило это.
Кристофер Пикслей, 03

Спасибо. Это сработало для меня в 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

Я обнаружил, что решение boliva отлично работает в симуляторе, но на устройстве подстрока фиксированной длины вызывает проблемы. Я написал более дружественное к Objective-C решение этой проблемы, которое работает на устройстве. В своей версии я конвертирую C-строку атрибутов в 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 = [attributes 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: attribute + 1 length: strlen (attribute) - 1 encoding: 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?
Alex Zavatone

Это звучит как хорошая идея, если я найду время, обновлю ответ с помощью этой опции.
PakitoV

Как только вы закончите с этим, я добавлю дамп метода, когда у меня будет время. Пришло время заняться самоанализом свойств реального объекта и метода поверх каждого NSObject.
Alex Zavatone

Я также работал над добавлением вывода значений, но похоже, что для некоторых структур (прямоугольников) тип является фактическим значением свойства. Так обстоит дело с caretRect tableViewController и другими беззнаковыми целыми числами в структуре viewController, возвращая c или f в качестве типа, который конфликтует с документами среды выполнения objective-C. Ясно, что здесь требуется дополнительная работа, чтобы завершить это. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone

Я посмотрел, но есть проблема, которую я не могу решить, чтобы сделать ее рекурсивной, мне нужно вызвать метод суперкласса (как в последней строке предыдущего кода), поскольку NSObject - это корневой класс, который не будет работать внутри категории . Таким образом, рекурсивность невозможна ... :( не уверен, что категория в NSObject - это еще один путь ...
PakitoV

3

Слово «атрибуты» немного расплывчато. Вы имеете в виду переменные экземпляра, свойства, методы, которые выглядят как аксессоры?

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

В целом, чтобы сделать это правильно, может потребоваться много работы или, по крайней мере, намного больше, чем хотелось бы большинству людей для того, что обычно сводится к действительно тривиальной функции.

В качестве альтернативы вы можете просто написать сценарий Ruby / Python, который просто читает файл заголовка и ищет все, что вы считаете «атрибутами» для класса.


Привет, Чак, спасибо за ответ. То, что я имел в виду под «атрибутами», действительно относилось к свойствам класса. Мне уже удалось добиться того, что я хотел, используя библиотеку времени выполнения Obj-C. Использование сценария для анализа файла заголовка не помогло бы мне во время выполнения.
boliva

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 / ОКР по поводу самоанализа ObjC "удивительными шарами" ... Они определенно не облегчают выполнение этого простого "взгляда" "на себя", "так сказать" ..

Если вы действительно хотите сходить с ума … проверьте .. class-dump , который является ошеломляющим безумным способом заглянуть в заголовки классов ЛЮБОГО исполняемого файла и т. Д. Он обеспечивает ГЛАГОЛЬНЫЙ взгляд на ваши классы… что я, лично я считаю действительно полезным - во многих, многих обстоятельствах. именно поэтому я начал искать решение вопроса ОП. вот некоторые параметры использования .. наслаждайтесь!

    -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. Итак, теперь вместо static 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

У примитивов есть свой тип, «@» обозначает объекты, а после него имя класса помещается в кавычки. Единственное исключение - id, который кодируется просто как "T @"
Михай Тимар

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.