Obtener una lista de propiedades de objeto en Objective-C

109

¿Cómo puedo obtener una lista (en forma de NSArrayo NSDictionary) de las propiedades de un objeto dado en Objective-C?

Imagine el siguiente escenario: he definido una clase padre que simplemente se extiende NSObject, que contiene un NSString, un BOOLy un NSDataobjeto como propiedades. Luego tengo varias clases que extienden esta clase principal, agregando muchas propiedades diferentes cada una.

¿Hay alguna forma de que pueda implementar un método de instancia en la clase principal que atraviese todo el objeto y devuelva, digamos, una NSArrayde cada una de las propiedades de la clase (secundaria) ya NSStringsque no están en la clase principal, para luego poder usar estas NSStringpara KVC?

boliva
fuente

Respuestas:

116

Me las arreglé para obtener la respuesta yo mismo. Al usar la biblioteca de tiempo de ejecución de Obj-C, tuve acceso a las propiedades de la manera que quería:

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

Esto requirió que hiciera una función C 'getPropertyType', que se toma principalmente de una muestra de código de Apple (no puedo recordar en este momento la fuente exacta):

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
fuente
5
+1 excepto que esto generará un error en primitivas, como int. Consulte mi respuesta a continuación para obtener una versión ligeramente mejorada de lo mismo.
jpswain
1
Como cuestión de corrección, [NSString stringWithCString:]se desaprueba a favor de [NSString stringWithCString:encoding:].
zekel
4
Debe importar el encabezado en tiempo de ejecución de objc #import <objc / runtime.h> Funciona en ARC.
Dae KIM
Aquí es cómo lograrlo utilizando Swift.
Ramis
76

La respuesta de @ boliva es buena, pero necesita un poco más para manejar primitivas, como int, long, float, double, etc.

Me basé en el suyo para agregar esta funcionalidad.

// 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
fuente
1
¿Tenía la intención de tenerlo #import <Foundation/Foundation.h>en la parte superior del archivo .h?
Andrew
2
[NSString stringWithUTF8String: propType] no pudo analizar "propType const char *" NSNumber \ x94 \ xfdk; "y devuelve una cadena nula ... No sé por qué es un NSNumber tan extraño. Mb porque ActiveRecord?
Dumoko
¡Magnífico! Muchas gracias.
Azik Abdullah
¡Esto es absolutamente perfecto!
Pranoy C
28

La respuesta de @ orange80 tiene un problema: en realidad, no siempre termina la cadena con 0. Esto puede llevar a resultados inesperados como fallar al intentar convertirlo a UTF8 (de hecho tuve un crashbug bastante molesto solo por eso. Fue divertido depurarlo ^^). Lo arreglé obteniendo un NSString del atributo y luego llamando a cStringUsingEncoding :. Esto funciona de maravilla ahora. (También funciona con ARC, al menos para mí)

Así que esta es mi versión del código ahora:

// 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
fuente
@farthen, ¿puede proporcionar un ejemplo que demuestre el problema con el código que proporcioné? Solo tengo curiosidad por verlo.
jpswain
@ orange80 Bueno, AFAIR, los datos nunca terminan en cero. Si es así, esto solo ocurre por accidente. Yo podría, sin embargo, estar equivocado. En otras noticias: todavía tengo este código en ejecución y funciona
perfectamente
@ orange80 Me encontré con este problema al intentar invocar su versión en IMAAdRequest de la biblioteca de anuncios IMA de Google. La solución de Farthen lo resolvió.
Christopher Pickslay
Gracias. Esto funcionó para mí en iOS7 cuando las dos respuestas anteriores no lo hicieron. +1 para todos los 3.
ChrisH
Esta es la única respuesta que funcionó para mí. Todo lo demás me estaba dando una rareza como "NSString \ x8d \ xc0 \ xd9" para los tipos de propiedad, presumiblemente porque el tamaño de char * estaba desactivado
Brian Colavito
8

Cuando probé con iOS 3.2, la función getPropertyType no funciona bien con la descripción de la propiedad. Encontré un ejemplo de la documentación de iOS: "Objective-C Runtime Programming Guide: Declared Properties".

Aquí hay un código revisado para el listado de propiedades en 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
fuente
7

Descubrí que la solución de boliva funciona bien en el simulador, pero en el dispositivo, la subcadena de longitud fija causa problemas. He escrito una solución más amigable con Objective-C para este problema que funciona en el dispositivo. En mi versión, convierto la C-String de los atributos en una NSString y realizo operaciones de cadena en ella para obtener una subcadena de solo la descripción del 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
fuente
Esto lanza una excepción EXC_BAD_ACCESS en NSRange const typeRangeStart = [atributos rangeOfString: @ "T @ \" "]; // inicio del tipo de cadena
Adam Mendoza
6

Esta implementación funciona tanto con tipos de objetos Objective-C como con primitivas C. Es compatible con iOS 8. Esta clase proporciona tres métodos de clase:

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

Devuelve un diccionario de todas las propiedades visibles de un objeto, incluidas las de todas sus superclases.

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

Devuelve un diccionario de todas las propiedades visibles de una clase, incluidas las de todas sus superclases.

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

Devuelve un diccionario de todas las propiedades visibles que son específicas de una subclase. No se incluyen las propiedades de sus superclases .

Un ejemplo útil del uso de estos métodos es copiar un objeto a una instancia de subclase en Objective-C sin tener que especificar las propiedades en un método de copia. . Partes de esta respuesta se basan en las otras respuestas a esta pregunta, pero proporciona una interfaz más limpia para la funcionalidad deseada.

Encabezamiento:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

Implementación:

//  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
fuente
Recibo una excepción EXC_BAD_ACCESS en esta línea NSString * nombre = [[NSString alloc] initWithBytes: atributo + 1 longitud: strlen (atributo) - 1 codificación: NSASCIIStringEncoding];
Adam Mendoza
4

Si alguien necesita obtener también las propiedades heredadas de las clases principales (como hice yo), aquí hay alguna modificación en el código " orange80 " para hacerlo 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
fuente
1
¿No podríamos hacer de esta una categoría y extender NSObject con ella para que esta funcionalidad esté integrada en cada clase que sea hija de NSObject?
Alex Zavatone
Eso suena como una buena idea, si puedo encontrar el tiempo actualizaré la respuesta con esa opción.
PakitoV
Una vez que haya terminado con eso, agregaré un volcado de método cuando tenga tiempo. Ya es hora de que tengamos propiedades de objetos reales e introspección de métodos sobre cada NSObject.
Alex Zavatone
También he estado trabajando para agregar valor de salida, pero parece que para algunas estructuras (rects), el tipo es el valor real de la propiedad. Este es el caso de caretRect de un tableViewController y otras entradas sin firmar en una estructura viewController devuelven cof como el tipo que entra en conflicto con los documentos de tiempo de ejecución de objetivo-C. Claramente, se necesita más trabajo aquí para completar esto. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone
Estaba echando un vistazo pero hay un problema que no puedo solucionar, para hacerlo recursivo necesito llamar al método para la superclase (como en la última línea del código anterior) ya que NSObject es la clase raíz que no funcionará dentro de una categoría . Entonces, no es posible la recursividad ... :( no estoy seguro de si una categoría en NSObject es el camino a seguir ...
PakitoV
3

La palabra "atributos" es un poco confusa. ¿Te refieres a variables de instancia, propiedades, métodos que parecen accesos?

La respuesta a las tres es "sí, pero no es muy fácil". La API de tiempo de ejecución de Objective-C incluye funciones para obtener la lista de ivar, la lista de métodos o la lista de propiedades para una clase (por ejemplo, class_copyPropertyList()), y luego una función correspondiente para cada tipo para obtener el nombre de un elemento en la lista (por ejemplo, property_getName()).

En general, puede ser mucho trabajo hacerlo bien, o al menos mucho más de lo que la mayoría de la gente querría hacer para lo que generalmente equivale a una característica realmente trivial.

Alternativamente, puede escribir un script Ruby / Python que solo lea un archivo de encabezado y busque lo que considere "atributos" para la clase.

Arrojar
fuente
Hola Chuck, gracias por tu respuesta. A lo que me refería con 'atributos' era de hecho a las propiedades de una clase. Ya logré lograr lo que quería haciendo uso de la biblioteca de tiempo de ejecución de Obj-C. Usar un script para analizar el archivo de encabezado no habría funcionado para lo que necesitaba en tiempo de ejecución.
boliva
3

Pude conseguir que la respuesta de @ orange80 funcionara CON ARC ENABLED ... ... para lo que quería, al menos ... pero no sin un poco de prueba y error. Con suerte, esta información adicional puede ahorrarle a alguien el dolor.

Guarde esas clases que describe en su respuesta = como una clase, y en su AppDelegate.h(o lo que sea), ponga #import PropertyUtil.h. Entonces en tu ...

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

método (o lo que sea)

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

El secreto es lanzar la variable de instancia de su clase ( en este caso, mi clase es Gist, y mi instancia de Gistesgist ) que desea consultar ... a NSObject ... (id), etc., no lo cortará ... por varios, raro , razones esotéricas. Esto le dará un resultado como ese ...

<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 los descarados / TOC alardeando sobre la introspección "asombrosa" de ObjC ... Seguro que no hacen que sea muy fácil realizar esta simple "mirada" "a uno mismo", "por así decirlo".

Sin embargo, si realmente quieres volverte loco ... echa un vistazo a ... class-dump , que es una manera increíblemente loca de echar un vistazo a los encabezados de clase de CUALQUIER ejecutable, etc. personalmente, encuentre realmente útil, en muchas, muchas circunstancias. en realidad, es por eso que comencé a buscar una solución a la pregunta del OP. aquí están algunos de los parámetros de uso ... ¡disfrútelo!

    -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 Grey
fuente
3

Tienes tres hechizos 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.

El siguiente fragmento de código puede ayudarlo.

-(void) displayClassInfo
{
    Class clazz = [self class];
    u_int count;

    Ivar* ivars = class_copyIvarList(clazz, &count);
    NSMutableArray* ivarArray = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count ; i++)
    {
        const char* ivarName = ivar_getName(ivars[i]);
        ivarArray addObject:[NSString  stringWithCString:ivarName encoding:NSUTF8StringEncoding]];
    }
    free(ivars);

    objc_property_t* properties = class_copyPropertyList(clazz, &count);
    NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count ; i++)
    {
        const char* propertyName = property_getName(properties[i]);
        [propertyArray addObject:[NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
    }
    free(properties);

    Method* methods = class_copyMethodList(clazz, &count);
    NSMutableArray* methodArray = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0; i < count ; i++)
    {
        SEL selector = method_getName(methods[i]);
        const char* methodName = sel_getName(selector);
        [methodArray addObject:[NSString  stringWithCString:methodName encoding:NSUTF8StringEncoding]];
    }
    free(methods);

    NSDictionary* classInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                           ivarArray, @"ivars",
                           propertyArray, @"properties",
                           methodArray, @"methods",
                           nil];

        NSLog(@"%@", classInfo);
}
Ans
fuente
2

Estaba usando la función boliva proporcionada, pero aparentemente dejó de funcionar con iOS 7. Así que ahora, en lugar de static const char * getPropertyType (propiedad objc_property_t), se puede usar lo siguiente:

- (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
fuente
Eres mi heroe. Todavía tengo que corregir manualmente algunas cosas (por alguna razón, los BOOL aparecen como 'Tc'), pero esto realmente me permitió hacer que las cosas funcionen nuevamente.
Harpastum
Los primitivos tienen su propio tipo, "@" denota objetos y después aparece el nombre de la clase entre comillas. La única excepción es la identificación, que está codificada simplemente como "T @"
Mihai Timar
2

Para los espectadores de Swift, puede obtener esta funcionalidad utilizando la Encodablefuncionalidad. Explicaré como:

  1. Conforme su objeto al Encodableprotocolo

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Crear extensión para Encodableproporcionar toDictionaryfuncionalidad.

     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. Llame toDictionarya su instancia de objeto y keyspropiedad de acceso .

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. ¡Voila! Accede a tus propiedades así:

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

Estas respuestas son útiles, pero necesito más de eso. Todo lo que quiero hacer es verificar si el tipo de clase de una propiedad es igual al de un objeto existente. Todos los códigos anteriores no son capaces de hacerlo, porque: Para obtener el nombre de clase de un objeto, object_getClassName () devuelve textos como estos:

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

Pero si invoca getPropertyType (...) desde el código de muestra anterior, con 4 estructuras objc_property_t de propiedades de una clase definida así:

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

devuelve cadenas respectivamente de la siguiente manera:

NSArray
NSArray
NSNumber
NSValue

Por lo tanto, no puede determinar si un NSObject es capaz de ser el valor de una propiedad de la clase. ¿Cómo hacer eso entonces?

Aquí está mi código de muestra completo (la función getPropertyType (...) es la misma que la anterior):

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