Obtenha uma lista de propriedades do objeto em Objective-C

109

Como posso obter uma lista (na forma de um NSArrayou NSDictionary) de propriedades de um determinado objeto em Objective-C?

Imagine o seguinte cenário: Eu defini uma classe pai que apenas se estende NSObject, que contém um NSString, um BOOLe um NSDataobjeto como propriedades. Então, eu tenho várias classes que estendem essa classe pai, adicionando muitas propriedades diferentes a cada uma.

Existe alguma maneira de implementar um método de instância na classe pai que atravessa todo o objeto e retorna, digamos, um NSArrayde cada uma das propriedades da classe (filha), uma vez NSStringsque não estão na classe pai, para que eu possa usá-los posteriormente NSStringpara KVC?

Boliva
fonte

Respostas:

116

Eu mesmo consegui obter a resposta. Usando a Biblioteca de Tempo de Execução Obj-C, tive acesso às propriedades da maneira que queria:

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

Isso exigiu que eu fizesse uma função C 'getPropertyType', que é principalmente tirada de uma amostra de código da Apple (não me lembro agora a fonte exata):

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
fonte
5
+1, exceto que isso causará um erro em primitivas, como int. Por favor, veja minha resposta abaixo para uma versão ligeiramente aprimorada dessa mesma coisa.
jpswain
1
Por uma questão de correção, [NSString stringWithCString:]é substituído em favor de [NSString stringWithCString:encoding:].
zekel
4
Deve importar o cabeçalho de tempo de execução objc #import <objc / runtime.h> Funciona em ARC.
Dae KIM
Aqui está como fazer isso usando o Swift.
Ramis de
76

A resposta de @boliva é boa, mas precisa de um pouco mais para lidar com primitivos, como int, long, float, double, etc.

Eu construí dele para adicionar essa funcionalidade.

// 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
fonte
1
Você pretendia colocar #import <Foundation/Foundation.h>no início do arquivo .h?
Andrew
2
[NSString stringWithUTF8String: propType] não conseguiu analisar "propType const char *" NSNumber \ x94 \ xfdk; "e retorna uma string nil ... Não sei por que é um NSNumber tão estranho. Mb porque ActiveRecord?
Dumoko
Excelente! Muito obrigado.
Azik Abdullah
Isso é absolutamente perfeito!
Pranoy C de
28

A resposta de @ orange80 tem um problema: na verdade, nem sempre termina a string com 0s. Isso pode levar a resultados inesperados, como travar ao tentar convertê-lo para UTF8 (na verdade, tive um crashbug muito chato só por causa disso. Foi divertido depurá-lo ^^). Corrigi-lo obtendo realmente um NSString do atributo e, em seguida, chamando cStringUsingEncoding :. Isso funciona como um encanto agora. (Também funciona com ARC, pelo menos para mim)

Portanto, esta é minha versão do código agora:

// 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
fonte
@farthen você pode fornecer um exemplo que demonstra o problema com o código que forneci? estou apenas curioso para ver.
jpswain
@ orange80 Bem, AFAIR os dados nunca terminam em zero. Se for, isso só acontece por acidente. Eu posso estar errado embora. Em outras notícias: Eu ainda tenho esse código em execução e ele funciona
perfeitamente
@ orange80 Encontrei este problema ao tentar invocar sua versão em IMAAdRequest da biblioteca de anúncios IMA do Google. A solução de Farthen resolveu.
Christopher Pickslay
Obrigado. Isso funcionou para mim no iOS7, quando as duas respostas anteriores não. +1 para todos os 3.
ChrisH
Esta é a única resposta que funcionou para mim. Todo o resto estava me dando como "NSString \ x8d \ xc0 \ xd9" estranheza para os tipos de propriedade, provavelmente porque o tamanho char * estava
errado
8

Quando tentei com o iOS 3.2, a função getPropertyType não funciona bem com a descrição da propriedade. Encontrei um exemplo na documentação do iOS: "Guia de programação em tempo de execução Objective-C: propriedades declaradas".

Aqui está um código revisado para a lista de propriedades no 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
fonte
7

Descobri que a solução do boliva funciona bem no simulador, mas no dispositivo a substring de comprimento fixo causa problemas. Eu escrevi uma solução mais amigável para o Objective-C para esse problema que funciona no dispositivo. Na minha versão, eu converto a C-String dos atributos em um NSString e executo operações de string nela para obter uma substring apenas com a descrição do tipo.

/*
 * @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
fonte
Isso lança uma exceção EXC_BAD_ACCESS em NSRange const typeRangeStart = [atributos rangeOfString: @ "T @ \" "]; // início do tipo string
Adam Mendoza
6

Esta implementação funciona com tipos de objeto Objective-C e primitivos C. É compatível com iOS 8. Esta classe fornece três métodos de classe:

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

Retorna um dicionário de todas as propriedades visíveis de um objeto, incluindo aquelas de todas as suas superclasses.

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

Retorna um dicionário de todas as propriedades visíveis de uma classe, incluindo aquelas de todas as suas superclasses.

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

Retorna um dicionário de todas as propriedades visíveis que são específicas de uma subclasse. Propriedades para suas superclasses não são incluídas.

Um exemplo útil do uso desses métodos é copiar um objeto para uma instância de subclasse em Objective-C sem ter que especificar as propriedades em um método de cópia . Partes desta resposta são baseadas em outras respostas a esta pergunta, mas fornece uma interface mais limpa para a funcionalidade desejada.

Cabeçalho:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

Implementação:

//  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
fonte
Recebo uma exceção EXC_BAD_ACCESS nesta linha NSString * name = [[NSString aloc] initWithBytes: atributo + 1 comprimento: strlen (atributo) - 1 codificação: NSASCIIStringEncoding];
Adam Mendoza,
4

Se alguém também precisa obter as propriedades herdadas das classes pai (como eu fiz), aqui estão algumas modificações no código " orange80 " para torná-lo recursivo:

+ (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
fonte
1
Não poderíamos transformar isso em uma categoria e estender NSObject com ela, de forma que essa funcionalidade seja incorporada a todas as classes que são filhas de NSObject?
Alex Zavatone
Parece uma boa ideia, se eu conseguir encontrar tempo atualizarei a resposta com essa opção.
PakitoV
Depois de fazer isso, adicionarei um método de despejo quando tiver tempo. Já era hora de colocarmos a propriedade do objeto real e a introspecção de método no topo de cada NSObject.
Alex Zavatone
Também tenho trabalhado para agregar valor à saída, mas parece que, para algumas estruturas (retos), o tipo é o valor real da propriedade. Este é o caso com o caretRect de um tableViewController e outros ints não assinados em um struct viewController retornam c ou f como o tipo que está em conflito com os documentos do Runtime C objetivo. Claramente, mais trabalho é necessário aqui para concluir isso. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone
Eu estava dando uma olhada, mas há um problema que não consigo contornar. Para torná-lo recursivo, preciso chamar o método da superclasse (como na última linha do código anterior), pois NSObject é a classe raiz que não funcionará dentro de uma categoria . Portanto, nenhuma recursividade possível ... :( não tenho certeza se uma categoria em NSObject é o caminho a seguir ...
PakitoV
3

A palavra "atributos" é um pouco confusa. Você quer dizer variáveis ​​de instância, propriedades, métodos que se parecem com acessores?

A resposta a todas as três é "sim, mas não é muito fácil". A API de tempo de execução Objective-C inclui funções para obter a lista ivar, lista de métodos ou lista de propriedades para uma classe (por exemplo, class_copyPropertyList()) e, em seguida, uma função correspondente para cada tipo para obter o nome de um item na lista (por exemplo, property_getName()).

Resumindo, pode ser muito trabalhoso acertar, ou pelo menos muito mais do que a maioria das pessoas gostaria de fazer para o que normalmente é um recurso realmente trivial.

Alternativamente, você pode simplesmente escrever um script Ruby / Python que apenas leia um arquivo de cabeçalho e procure por qualquer coisa que você considere "atributos" para a classe.

Mandril
fonte
Oi chuck, obrigado pela sua resposta. O que eu estava me referindo com 'atributos' era de fato as propriedades de uma classe. Eu já consegui fazer o que queria usando a Biblioteca de Tempo de Execução Obj-C. Usar um script para analisar o arquivo de cabeçalho não teria funcionado para o que eu precisava no tempo de execução.
boliva de
3

Consegui fazer com que a resposta de @ orange80 funcionasse COM ARC ATIVADO ... ... para o que eu queria - pelo menos ... mas não sem um pouco de tentativa e erro. Esperançosamente, esta informação adicional pode poupar alguém do sofrimento.

Salve as classes que ele descreve em sua resposta = como uma classe e em sua AppDelegate.h(ou qualquer outra), coloque #import PropertyUtil.h. Então em seu ...

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

método (ou qualquer outro)

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

O segredo é lançar a variável de instância de sua classe ( neste caso, minha classe é Gist, e minha instância Gistégist ) que você deseja consultar ... para NSObject ... (id), etc, não vai funcionar .. para vários, estranho , razões esotéricas. Isso lhe dará alguma saída como ...

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

Para todos os descarados / TOC da Apple se gabando da introspecção "espantosa" "do ObjC ... Eles certamente não tornam muito fácil realizar este simples" olhar "" para si mesmo "," por assim dizer "..

Se você realmente quer ficar louco .. confira .. class-dump , que é uma maneira absurdamente insana de espiar nos cabeçalhos das classes de QUALQUER executável, etc ... Fornece uma visão VERBOSA de suas classes ... que eu, pessoalmente, ache verdadeiramente útil - em muitas, muitas circunstâncias. na verdade é por isso que comecei a buscar uma solução para a questão do OP. aqui estão alguns dos parâmetros de uso .. divirta-se!

    -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
fonte
3

Você tem três feitiços mágicos

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.

O seguinte código pode ajudá-lo.

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

Eu estava usando a função boliva fornecida, mas aparentemente ela parou de funcionar com o iOS 7. Agora, em vez de const char * getPropertyType (propriedade objc_property_t), pode-se usar apenas o seguinte:

- (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
fonte
Voce é meu herói. Eu ainda tenho que corrigir manualmente algumas coisas (por algum motivo BOOLs estão aparecendo como 'Tc'), mas isso realmente me permitiu fazer as coisas funcionarem novamente.
Harpastum de
Os primitivos têm seu próprio tipo, "@" denota objetos e depois disso o nome da classe aparece entre aspas. A única exceção é id, que é codificado simplesmente como "T @"
Mihai Timar
2

Para espectadores do Swift, você pode obter essa funcionalidade utilizando a Encodablefuncionalidade. Vou explicar como:

  1. Adapte seu objeto ao Encodableprotocolo

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Crie uma extensão para Encodablefornecer toDictionaryfuncionalidade

     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. Chame toDictionarysua instância de objeto e keyspropriedade de acesso .

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Voila! Acesse suas propriedades da seguinte forma:

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

Essas respostas são úteis, mas preciso mais disso. Tudo o que quero fazer é verificar se o tipo de classe de uma propriedade é igual ao de um objeto existente. Todos os códigos acima não são capazes de fazer isso, porque: Para obter o nome da classe de um objeto, object_getClassName () retorna textos como estes:

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

Mas se invocar getPropertyType (...) do código de amostra acima, com 4 estruturas objc_property_t de propriedades de uma classe definida desta forma:

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

ele retorna strings respectivamente como a seguir:

NSArray
NSArray
NSNumber
NSValue

Portanto, não é possível determinar se um NSObject é capaz de ser o valor de uma propriedade da classe. Como fazer isso então?

Aqui está meu código de amostra completo (a função getPropertyType (...) é a mesma acima):

#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
fonte