Obtenir une liste de propriétés d'objet dans Objective-C

109

Comment puis-je obtenir une liste (sous la forme d'un NSArrayou NSDictionary) des propriétés d'un objet donné dans Objective-C?

Imaginez le scénario suivant: j'ai défini une classe parente qui ne fait qu'étendre NSObject, qui contient un NSString, un BOOLet un NSDataobjet comme propriétés. Ensuite, j'ai plusieurs classes qui étendent cette classe parente, en ajoutant chacune de nombreuses propriétés différentes.

Y a-t-il un moyen pour que je puisse implémenter une méthode d'instance sur la classe parent qui traverse l'objet entier et retourne, disons, une NSArrayde chacune des propriétés de la classe (enfant) car NSStringsqui ne sont pas sur la classe parent, afin que je puisse les utiliser plus tard NSStringpour KVC?

boliva
la source

Réponses:

116

J'ai juste réussi à obtenir la réponse moi-même. En utilisant la bibliothèque d'exécution Obj-C, j'ai eu accès aux propriétés comme je le voulais:

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

Cela m'a obligé à créer une fonction C 'getPropertyType', qui est principalement extraite d'un exemple de code Apple (je ne me souviens pas pour le moment de la source exacte):

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
la source
5
+1 sauf que cela provoquera une erreur sur les primitives, telles que int. Veuillez voir ma réponse ci-dessous pour une version légèrement améliorée de cette même chose.
jpswain
1
Par souci d'exactitude, [NSString stringWithCString:]est déconseillé en faveur de [NSString stringWithCString:encoding:].
zekel
4
Devrait importer l'en-tête d'exécution d'objc #import <objc / runtime.h> Cela fonctionne sur ARC.
Dae KIM
Voici comment y parvenir en utilisant Swift.
Ramis
76

La réponse de @ boliva est bonne, mais nécessite un petit supplément pour gérer les primitives, comme int, long, float, double, etc.

J'ai construit sur lui pour ajouter cette fonctionnalité.

// 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
la source
1
Aviez-vous l'intention d'avoir #import <Foundation/Foundation.h>en haut du fichier .h?
Andrew
2
[NSString stringWithUTF8String: propType] n'a pas pu analyser "propType const char *" NSNumber \ x94 \ xfdk; "et renvoie une chaîne nulle ... Je ne sais pas pourquoi c'est un NSNumber si étrange. Mb parce que ActiveRecord?
Dumoko
Superbe! Merci beaucoup.
Azik Abdullah
C'est absolument parfait!
Pranoy C
28

La réponse de @ orange80 a un problème: elle ne termine pas toujours la chaîne par des 0. Cela peut conduire à des résultats inattendus comme un crash en essayant de le convertir en UTF8 (en fait j'ai eu un crashbug assez ennuyeux juste à cause de ça. C'était amusant de le déboguer ^^). Je l'ai corrigé en obtenant un NSString à partir de l'attribut, puis en appelant cStringUsingEncoding :. Cela fonctionne comme un charme maintenant. (Fonctionne également avec ARC, du moins pour moi)

Voici donc ma version du code maintenant:

// 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
la source
@farthen pouvez-vous fournir un exemple qui illustre le problème avec le code que j'ai fourni? je suis juste curieux de le voir.
jpswain
@ orange80 Eh bien, AFAIR, les données ne sont jamais terminées par zéro. Si c'est le cas, cela n'arrive que par accident. Je me trompe peut-être cependant. Dans d'autres nouvelles: j'ai toujours ce code en cours d'exécution et il fonctionne à toute
épreuve
@ orange80 J'ai rencontré ce problème en essayant d'appeler votre version sur IMAAdRequest à partir de la bibliothèque d'annonces IMA de Google. la solution de Farthen l'a résolu.
Christopher Pickslay
Merci. Cela a fonctionné pour moi dans iOS7, contrairement aux deux réponses précédentes. +1 pour tous les 3.
ChrisH
C'est la seule réponse qui a fonctionné pour moi. Tout le reste me donnait une bizarrerie comme "NSString \ x8d \ xc0 \ xd9" pour les types de propriétés, probablement parce que le dimensionnement du char * était désactivé
Brian Colavito
8

Lorsque j'ai essayé avec iOS 3.2, la fonction getPropertyType ne fonctionne pas bien avec la description de la propriété. J'ai trouvé un exemple de la documentation iOS: "Objective-C Runtime Programming Guide: Declared Properties".

Voici un code révisé pour la liste des propriétés dans 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
la source
7

J'ai trouvé que la solution de boliva fonctionne bien dans le simulateur, mais sur l'appareil, la sous-chaîne de longueur fixe pose des problèmes. J'ai écrit une solution plus conviviale pour Objective-C à ce problème qui fonctionne sur l'appareil. Dans ma version, je convertis la chaîne C des attributs en NSString et j'effectue des opérations sur la chaîne pour obtenir une sous-chaîne de la description de type uniquement.

/*
 * @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
la source
Cela lance une exception EXC_BAD_ACCESS sur NSRange const typeRangeStart = [attributes rangeOfString: @ "T @ \" "]; // début de la chaîne de type
Adam Mendoza
6

Cette implémentation fonctionne avec les types d'objet Objective-C et les primitives C. Il est compatible iOS 8. Cette classe fournit trois méthodes de classe:

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

Renvoie un dictionnaire de toutes les propriétés visibles d'un objet, y compris celles de toutes ses superclasses.

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

Renvoie un dictionnaire de toutes les propriétés visibles d'une classe, y compris celles de toutes ses superclasses.

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

Renvoie un dictionnaire de toutes les propriétés visibles spécifiques à une sous-classe. Les propriétés de ses superclasses ne sont pas incluses.

Un exemple utile de l'utilisation de ces méthodes est de copier un objet dans une instance de sous-classe en Objective-C sans avoir à spécifier les propriétés dans une méthode de copie . Certaines parties de cette réponse sont basées sur les autres réponses à cette question, mais elle fournit une interface plus claire à la fonctionnalité souhaitée.

Entête:

//  SYNUtilities.h

#import <Foundation/Foundation.h>

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

La mise en oeuvre:

//  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
la source
J'obtiens une exception EXC_BAD_ACCESS sur cette ligne NSString * name = [[NSString alloc] initWithBytes: attribute + 1 length: strlen (attribut) - 1 encoding: NSASCIIStringEncoding];
Adam Mendoza
4

Si quelqu'un a besoin d'obtenir également les propriétés héritées des classes parentes (comme je l'ai fait), voici quelques modifications sur le code " orange80 " pour le rendre récursif:

+ (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
la source
1
Ne pourrions-nous pas en faire une catégorie et étendre NSObject avec elle afin que cette fonctionnalité soit intégrée dans chaque classe qui est un enfant de NSObject?
Alex Zavatone
Cela semble être une bonne idée, si je peux trouver le temps, je mettrai à jour la réponse avec cette option.
PakitoV
Une fois que vous avez terminé, j'ajouterai un vidage de méthode lorsque j'aurai le temps. Il est grand temps que nous ayons une véritable propriété d'objet et une introspection de méthode sur chaque NSObject.
Alex Zavatone
J'ai également travaillé sur l'ajout de valeur ajoutée, mais il semble que pour certaines structures (rects), le type est la valeur réelle de la propriété. C'est le cas avec le caretRect d'un tableViewController et d'autres entiers non signés dans une structure viewController retournent c ou f comme type qui entre en conflit avec la documentation objective-C Runtime. Il est clair que plus de travail est nécessaire ici pour que cela soit complet. developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Alex Zavatone
Je regardais mais il y a un problème que je ne peux pas contourner, pour le rendre récursif, je dois appeler la méthode pour la superclasse (comme dans la dernière ligne du code précédent) car NSObject est la classe racine qui ne fonctionnera pas dans une catégorie . Donc pas de récursivité possible ...: (je ne sais pas si une catégorie dans NSObject est la voie à suivre ...
PakitoV
3

Le mot «attributs» est un peu flou. Voulez-vous dire des variables d'instance, des propriétés, des méthodes qui ressemblent à des accesseurs?

La réponse à ces trois questions est «oui, mais ce n'est pas très facile». L' API d'exécution Objective-C comprend des fonctions pour obtenir la liste ivar, la liste des méthodes ou la liste des propriétés d'une classe (par exemple, class_copyPropertyList()), puis une fonction correspondante pour chaque type pour obtenir le nom d'un élément dans la liste (par exemple, property_getName()).

Dans l'ensemble, cela peut demander beaucoup de travail pour bien faire les choses, ou du moins beaucoup plus que ce que la plupart des gens voudraient faire pour ce qui est généralement une fonctionnalité vraiment triviale.

Alternativement, vous pouvez simplement écrire un script Ruby / Python qui lit simplement un fichier d'en-tête et recherche tout ce que vous considérez comme des «attributs» pour la classe.

Mandrin
la source
Salut chuck, merci pour votre réponse. Ce à quoi je faisais référence avec les «attributs» était en fait une classe de propriétés. J'ai déjà réussi à accomplir ce que je voulais en utilisant la bibliothèque d'exécution Obj-C. L'utilisation d'un script pour analyser le fichier d'en-tête n'aurait pas fonctionné pour ce dont j'avais besoin à l'exécution.
boliva
3

J'ai pu faire fonctionner la réponse de @ orange80 AVEC ARC ENABLED ... ... pour ce que je voulais - du moins ... mais pas sans un peu d'essais et d'erreurs. Espérons que ces informations supplémentaires peuvent épargner à quelqu'un le chagrin.

Enregistrez les classes qu'il décrit dans sa réponse = en tant que classe, et dans votre AppDelegate.h(ou autre), mettez #import PropertyUtil.h. Puis dans votre ...

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

méthode (ou autre)

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

Le secret est de convertir la variable d'instance de votre classe ( dans ce cas, ma classe est Gist, et mon instance de Gistestgist ) que vous voulez interroger ... en NSObject ... (id), etc., ne la coupera pas .. pour divers, bizarre , des raisons ésotériques. Cela vous donnera une sortie comme ça ...

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

Pour toutes les vantardises d'Apple / TOC à propos de l'introspection "amazeballs" "d'ObjC ... Ils ne rendent certainement pas très facile d'effectuer ce simple" regard "" sur soi-même "," pour ainsi dire "..

Si vous voulez vraiment devenir sauvage par contre ... consultez .. class-dump , qui est un moyen incroyablement insensé de jeter un coup d'œil dans les en-têtes de classe de TOUT exécutable, etc. Cela fournit un regard VERBOSE sur vos classes ... personnellement, trouvez vraiment utile - dans de très nombreuses circonstances. c'est en fait pourquoi j'ai commencé à chercher une solution à la question du PO. voici quelques paramètres d'utilisation .. profitez-en!

    -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
la source
3

Vous avez trois sorts magiques

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.

Le morceau de code suivant peut vous aider.

-(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
la source
2

J'utilisais la fonction boliva fournie, mais apparemment elle a cessé de fonctionner avec iOS 7. Alors maintenant, au lieu de static const char * getPropertyType (propriété objc_property_t), on peut simplement utiliser ce qui suit:

- (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
la source
Tu es mon héro. Je dois encore corriger manuellement certaines choses (pour une raison quelconque, les BOOL deviennent 'Tc'), mais cela m'a en fait permis de remettre les choses en marche.
Harpastum
Les primitives ont leur propre type, "@" désigne les objets et après cela, le nom de la classe apparaît entre guillemets. La seule exception est id qui est encodé simplement comme "T @"
Mihai Timar
2

Pour les spectateurs Swift, vous pouvez obtenir cette fonctionnalité en utilisant la Encodablefonctionnalité. Je vais vous expliquer comment:

  1. Conformez votre objet au Encodableprotocole

    class ExampleObj: NSObject, Encodable {
        var prop1: String = ""
        var prop2: String = ""
    }
  2. Créer une extension pour Encodablefournir des toDictionaryfonctionnalités

     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. Appelez toDictionaryvotre instance d'objet et accédez à la keyspropriété.

    let exampleObj = ExampleObj()
    exampleObj.toDictionary()?.keys
  4. Voila! Accédez à vos propriétés comme ceci:

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

Ces réponses sont utiles, mais j'en demande davantage. Tout ce que je veux faire, c'est vérifier si le type de classe d'une propriété est égal à celui d'un objet existant. Tous les codes ci-dessus ne sont pas capables de le faire, car: Pour obtenir le nom de classe d'un objet, object_getClassName () renvoie des textes comme ceux-ci:

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

Mais si vous appelez getPropertyType (...) à partir de l'exemple de code ci-dessus, avec 4 structures objc_property_t de propriétés d'une classe définie comme ceci:

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

il renvoie les chaînes respectivement comme suit:

NSArray
NSArray
NSNumber
NSValue

Il n'est donc pas en mesure de déterminer si un NSObject est capable d'être la valeur d'une propriété de la classe. Comment faire ça alors?

Voici mon exemple de code complet (la fonction getPropertyType (...) est la même que ci-dessus):

#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
la source