Ottieni un elenco delle proprietà degli oggetti in Objective-C


109

Come posso ottenere un elenco (sotto forma di NSArrayo NSDictionary) delle proprietà di un dato oggetto in Objective-C?

Immagina il seguente scenario: ho definito una classe genitore che si estende solo NSObject, che contiene una NSString, una BOOLe un NSDataoggetto come proprietà. Quindi ho diverse classi che estendono questa classe genitore, aggiungendo molte proprietà diverse ciascuna.

C'è un modo per implementare un metodo di istanza sulla classe genitore che passa attraverso l'intero oggetto e restituisce, ad esempio, una NSArraydi ciascuna delle proprietà della classe (figlio) in quanto nonNSStrings sono sulla classe genitore, quindi posso usarle in seguito per KVC?NSString

Risposte:


116

Sono appena riuscito a ottenere la risposta da solo. Utilizzando la libreria di runtime Obj-C, ho avuto accesso alle proprietà nel modo desiderato:

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

Ciò mi ha richiesto di creare una funzione C 'getPropertyType', che è principalmente presa da un esempio di codice Apple (al momento non ricordo la fonte esatta):

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 eccetto questo errore sulle primitive, come int. Si prega di vedere la mia risposta di seguito per una versione leggermente migliorata di questa stessa cosa.
jpswain

1
Per una questione di correttezza, [NSString stringWithCString:]è deprecato a favore di [NSString stringWithCString:encoding:].
zekel

4
Dovrebbe importare l'intestazione del runtime objc #import <objc / runtime.h> Funziona su ARC.
Dae KIM

Ecco come realizzarlo usando Swift.
Ramis

76

La risposta di @ boliva è buona, ma ha bisogno di un piccolo extra per gestire le primitive, come int, long, float, double, ecc.

Ho costruito il suo per aggiungere questa funzionalità.

// 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
Avevi intenzione di avere #import <Foundation/Foundation.h>all'inizio del file .h?
Andrew

2
[NSString stringWithUTF8String: propType] non ha potuto analizzare "propType const char *" NSNumber \ x94 \ xfdk; "e restituisce una stringa nulla ... Non so perché è un NSNumber così strano. Mb perché ActiveRecord?
Dumoko

Stupendo! Molte grazie.
Azik Abdullah

Questo è assolutamente perfetto!
Pranoy C

28

La risposta di @ orange80 ha un problema: in realtà non sempre termina la stringa con 0. Questo può portare a risultati imprevisti come l'arresto anomalo durante il tentativo di convertirlo in UTF8 (in realtà ho avuto un crashbug piuttosto fastidioso proprio per questo. È stato divertente eseguirne il debug ^^). L'ho risolto ottenendo effettivamente un NSString dall'attributo e quindi chiamando cStringUsingEncoding :. Ora funziona come un incantesimo. (Funziona anche con ARC, almeno per me)

Quindi questa è la mia versione del codice ora:

// 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 puoi fornire un esempio che dimostri il problema con il codice che ho fornito? sono solo curioso di vederlo.
jpswain

@ orange80 Bene, AFAIR i dati non sono mai terminati da zero. Se è questo accade solo per caso. Potrei sbagliarmi però. In altre notizie: ho ancora questo codice in esecuzione e funziona come una roccia: p
felinira

@ orange80 Ho riscontrato questo problema cercando di richiamare la tua versione su IMAAdRequest dalla libreria di annunci IMA di Google. La soluzione di Farthen lo risolse.
Christopher Pickslay

Grazie. Questo ha funzionato per me in iOS7 quando le due risposte precedenti no. +1 per tutti i 3.
ChrisH

Questa è l'unica risposta che ha funzionato per me. Tutto il resto mi dava stranezze tipo "NSString \ x8d \ xc0 \ xd9" per i tipi di proprietà, presumibilmente perché il dimensionamento dei caratteri * non era valido
Brian Colavito

8

Quando ho provato con iOS 3.2, la funzione getPropertyType non funziona bene con la descrizione della proprietà. Ho trovato un esempio dalla documentazione iOS: "Objective-C Runtime Programming Guide: Declared Properties".

Ecco un codice rivisto per l'elenco delle proprietà in 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

Ho scoperto che la soluzione di Boliva funziona bene nel simulatore, ma sul dispositivo la sottostringa di lunghezza fissa causa problemi. Ho scritto una soluzione più obiettiva-C a questo problema che funziona sul dispositivo. Nella mia versione, converto la stringa C degli attributi in una NSString ed eseguo operazioni sulla stringa per ottenere una sottostringa della sola descrizione 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;
}

Questo genera un'eccezione EXC_BAD_ACCESS su NSRange const typeRangeStart = [attributes rangeOfString: @ "T @ \" "]; // inizio della stringa di tipo
Adam Mendoza

6

Questa implementazione funziona sia con i tipi di oggetto Objective-C che con le primitive C. È compatibile con iOS 8. Questa classe fornisce tre metodi di classe:

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

Restituisce un dizionario di tutte le proprietà visibili di un oggetto, comprese quelle di tutte le sue superclassi.

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

Restituisce un dizionario di tutte le proprietà visibili di una classe, comprese quelle di tutte le sue superclassi.

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

Restituisce un dizionario di tutte le proprietà visibili che sono specifiche di una sottoclasse. Le proprietà per le sue superclassi non sono incluse.

Un utile esempio dell'uso di questi metodi è copiare un oggetto in un'istanza di sottoclasse in Objective-C senza dover specificare le proprietà in un metodo di copia . Parti di questa risposta si basano sulle altre risposte a questa domanda, ma fornisce un'interfaccia più pulita per la funzionalità desiderata.

Intestazione:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

Implementazione:

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

Ottengo un'eccezione EXC_BAD_ACCESS su questa riga NSString * name = [[NSString alloc] initWithBytes: attribute + 1 length: strlen (attribute) - 1 encoding: NSASCIIStringEncoding];
Adam Mendoza

4

Se qualcuno ha bisogno di ottenerlo le proprietà ereditate dalle classi genitore (come ho fatto io) ecco alcune modifiche sul codice " orange80 " per renderlo ricorsivo:

+ (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
Non potremmo renderla una categoria ed estendere NSObject con essa in modo che questa funzionalità sia incorporata in ogni classe figlia di NSObject?
Alex Zavatone

Sembra una buona idea, se riesco a trovare il tempo aggiornerò la risposta con quell'opzione.
PakitoV

Una volta che hai finito, aggiungerò un dump del metodo quando avrò tempo. È giunto il momento di ottenere la proprietà dell'oggetto reale e l'introspezione del metodo su ogni NSObject.
Alex Zavatone

Ho anche lavorato sull'aggiunta di valore in uscita, ma sembra che per alcune strutture (rects), il tipo sia il valore effettivo della proprietà. Questo è il caso del caretRect di un tableViewController e altri int senza segno in una struttura viewController restituiscono c o f come il tipo che è in conflitto con i documenti di Runtime Object-C. Chiaramente è necessario più lavoro qui per completare questo. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone

Stavo dando un'occhiata ma c'è un problema che non riesco a risolvere, per renderlo ricorsivo devo chiamare il metodo per la superclasse (come nell'ultima riga del codice precedente) poiché NSObject è la classe root che non funzionerà all'interno di una categoria . Quindi nessuna ricorsività possibile ... :( non sono sicuro che una categoria in NSObject sia la strada da percorrere più ...
PakitoV

3

La parola "attributi" è un po 'confusa. Intendi variabili di istanza, proprietà, metodi che sembrano funzioni di accesso?

La risposta a tutti e tre è "sì, ma non è molto facile". L' API runtime Objective-C include funzioni per ottenere l'elenco ivar, l'elenco dei metodi o l'elenco delle proprietà per una classe (ad esempio class_copyPropertyList()), e quindi una funzione corrispondente per ogni tipo per ottenere il nome di un elemento nell'elenco (ad esempio, property_getName()).

Tutto sommato, può essere una specie di lavoro per farlo bene, o almeno molto di più di quanto la maggior parte delle persone vorrebbe fare per quella che di solito equivale a una caratteristica davvero banale.

In alternativa, potresti semplicemente scrivere uno script Ruby / Python che legga semplicemente un file di intestazione e cerchi qualunque cosa tu consideri "attributi" per la classe.


Ciao Chuck, grazie per la tua risposta. Quello a cui mi riferivo con "attributi" era in effetti una proprietà di classe. Sono già riuscito a realizzare ciò che volevo utilizzando la libreria di runtime Obj-C. L'utilizzo di uno script per analizzare il file di intestazione non avrebbe funzionato per ciò di cui avevo bisogno in runtime.
boliva

3

Sono riuscito a far funzionare la risposta di @ orange80 CON ARC ENABLED ... ... per quello che volevo - almeno ... ma non senza un po 'di tentativi ed errori. Si spera che queste informazioni aggiuntive possano risparmiare a qualcuno il dolore.

Salva quelle classi che descrive nella sua risposta = come una classe e nel tuo AppDelegate.h(o qualsiasi altra cosa), put #import PropertyUtil.h. Quindi nel tuo ...

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

metodo (o qualsiasi altra cosa)

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

Il segreto è eseguire il cast della variabile di istanza della tua classe ( in questo caso la mia classe è Gist, e la mia istanza di Gistègist ) che vuoi interrogare ... su NSObject ... (id), ecc, non la taglierà .. per vari, strani , ragioni esoteriche. Questo ti darà un output in questo modo ...

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

Per tutti gli sfacciati / OCD di Apple che si vantano dell'introspezione "stupefacenti" di ObjC ... Di certo non rendono molto facile eseguire questo semplice "sguardo" "a se stessi", "per così dire" ..

Se vuoi davvero scatenarti, dai un'occhiata .. class-dump , che è un modo incredibilmente folle per sbirciare nelle intestazioni di classe di QUALSIASI eseguibile, ecc ... Fornisce uno sguardo VERBOSO alle tue classi ... che io, personalmente, trovo veramente utile - in molte, molte circostanze. in realtà è per questo che ho iniziato a cercare una soluzione alla domanda dell'OP. ecco alcuni parametri di utilizzo .. buon divertimento!

    -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

Hai tre incantesimi magici

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.

La seguente parte di codice può aiutarti.

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

Stavo usando la funzione boliva fornita, ma apparentemente ha smesso di funzionare con iOS 7. Quindi ora invece di static const char * getPropertyType (proprietà objc_property_t) si può semplicemente usare quanto segue:

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

Sei il mio eroe. Devo ancora correggere manualmente alcune cose (per qualche motivo i BOOL vengono visualizzati come "Tc"), ma questo in realtà mi ha permesso di far funzionare di nuovo le cose.
Harpastum

Le primitive hanno il loro tipo, "@" denota gli oggetti e dopo di esso il nome della classe appare tra virgolette. L'unica eccezione è l'id che è codificato semplicemente come "T @"
Mihai Timar

2

Per gli spettatori di Swift, puoi ottenere questa funzionalità utilizzando la Encodablefunzionalità. Spiegherò come:

  1. Conforma il tuo oggetto al Encodableprotocollo

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Crea estensione per Encodablefornire toDictionaryfunzionalità

     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. Chiama toDictionaryl'istanza dell'oggetto e accedi alla keysproprietà.

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Ecco! Accedi alle tue proprietà in questo modo:

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

1

Queste risposte sono utili, ma ne ho bisogno di più. Tutto quello che voglio fare è verificare se il tipo di classe di una proprietà è uguale a quello di un oggetto esistente. Tutti i codici di cui sopra non sono in grado di farlo, perché: Per ottenere il nome della classe di un oggetto, object_getClassName () restituisce testi come questi:

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

Ma se si richiama getPropertyType (...) dal codice di esempio sopra, con 4 strutture objc_property_t di proprietà di una classe definita in questo modo:

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

restituisce stringhe rispettivamente come segue:

NSArray
NSArray
NSNumber
NSValue

Quindi non è in grado di determinare se un NSObject è in grado di essere il valore di una proprietà della classe. Come farlo allora?

Ecco il mio codice di esempio completo (la funzione getPropertyType (...) è la stessa di sopra):

#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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.