web-dev-qa-db-fra.com

Introspection / réflexion Objective-C

Existe-t-il une méthode, une fonction, une API, une méthode communément acceptée, etc. pour vider le contenu d'un objet instancié dans Objective-C, en particulier dans l'environnement Cocoa/Cocoa-Touch d'Apple?

Je veux pouvoir faire quelque chose comme

MyType *the_thing = [[MyType alloc] init];
NSString *the_dump = [the_thing dump]; //pseudo code
NSLog("Dumped Contents: %@", the_dump);

et afficher les noms et valeurs des variables d'instance de l'objet, ainsi que toutes les méthodes disponibles pour appeler au moment de l'exécution. Idéalement dans un format facile à lire.

Pour les développeurs familiers avec PHP, je recherche essentiellement l'équivalent des fonctions de réflexion (var_dump(), get_class_methods()) et de OO API de réflexion).

78
Alan Storm

MISE À JOUR: Quiconque cherche à faire ce genre de choses pourrait vouloir vérifier wrapper ObjC de Mike Ash pour le runtime Objective-C .

Voici plus ou moins comment vous vous y prendriez:

#import <objc/runtime.h>

. . . 

-(void)dumpInfo
{
    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* classDump = [NSDictionary dictionaryWithObjectsAndKeys:
                               ivarArray, @"ivars",
                               propertyArray, @"properties",
                               methodArray, @"methods",
                               nil];

    NSLog(@"%@", classDump);
}

À partir de là, il est facile d'obtenir les valeurs réelles des propriétés d'une instance, mais vous devez vérifier s'il s'agit de types ou d'objets primitifs, donc j'étais trop paresseux pour les insérer. Vous pouvez également choisir d'analyser la chaîne d'héritage pour obtenir toutes les propriétés définies sur un objet. Ensuite, il existe des méthodes définies sur les catégories, et plus encore ... Mais presque tout est facilement disponible.

Voici un extrait de ce que le code ci-dessus vide pour UILabel:

{
    ivars =     (
        "_size",
        "_text",
        "_color",
        "_highlightedColor",
        "_shadowColor",
        "_font",
        "_shadowOffset",
        "_minFontSize",
        "_actualFontSize",
        "_numberOfLines",
        "_lastLineBaseline",
        "_lineSpacing",
        "_textLabelFlags"
    );
    methods =     (
        rawSize,
        "setRawSize:",
        "drawContentsInRect:",
        "textRectForBounds:",
        "textSizeForWidth:",
        . . .
    );
    properties =     (
        text,
        font,
        textColor,
        shadowColor,
        shadowOffset,
        textAlignment,
        lineBreakMode,
        highlightedTextColor,
        highlighted,
        enabled,
        numberOfLines,
        adjustsFontSizeToFitWidth,
        minimumFontSize,
        baselineAdjustment,
        "_lastLineBaseline",
        lineSpacing,
        userInteractionEnabled
    );
}
112
Felixyz

À court de la méthode description (comme .toString () en Java), je n'en ai pas entendu parler, mais il ne serait pas trop difficile d'en créer une. The Objective-C Runtime Reference a un tas de fonctions que vous pouvez utiliser pour obtenir des informations sur les variables d'instance d'un objet, les méthodes, les propriétés, etc.

12
Dave DeLong

Voici ce que j'utilise actuellement pour imprimer automatiquement les variables de classe, dans une bibliothèque pour une éventuelle publication publique - cela fonctionne en vidant toutes les propriétés de la classe d'instance tout en remontant l'arborescence d'héritage. Grâce à KVC, vous n'avez pas besoin de vous soucier si une propriété est de type primitif ou non (pour la plupart des types).

// Finds all properties of an object, and prints each one out as part of a string describing the class.
+ (NSString *) autoDescribe:(id)instance classType:(Class)classType
{
    NSUInteger count;
    objc_property_t *propList = class_copyPropertyList(classType, &count);
    NSMutableString *propPrint = [NSMutableString string];

    for ( int i = 0; i < count; i++ )
    {
        objc_property_t property = propList[i];

        const char *propName = property_getName(property);
        NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding];

        if(propName) 
        {
            id value = [instance valueForKey:propNameString];
            [propPrint appendString:[NSString stringWithFormat:@"%@=%@ ; ", propNameString, value]];
        }
    }
    free(propList);


    // Now see if we need to map any superclasses as well.
    Class superClass = class_getSuperclass( classType );
    if ( superClass != nil && ! [superClass isEqual:[NSObject class]] )
    {
        NSString *superString = [self autoDescribe:instance classType:superClass];
        [propPrint appendString:superString];
    }

    return propPrint;
}

+ (NSString *) autoDescribe:(id)instance
{
    NSString *headerString = [NSString stringWithFormat:@"%@:%p:: ",[instance class], instance];
    return [headerString stringByAppendingString:[self autoDescribe:instance classType:[instance class]]];
}

J'ai apporté quelques modifications au code de Kendall pour l'impression des valeurs de propriété, ce qui m'a été très utile. Je l'ai définie comme une méthode d'instance au lieu d'une méthode de classe, car c'est ainsi que la récursion de la superclasse l'appelle. J'ai également ajouté la gestion des exceptions pour les propriétés non conformes à KVO et ajouté des sauts de ligne à la sortie pour la rendre plus facile à lire (et à différencier):

-(NSString *) autoDescribe:(id)instance classType:(Class)classType
{
    NSUInteger count;
    objc_property_t *propList = class_copyPropertyList(classType, &count);
    NSMutableString *propPrint = [NSMutableString string];

    for ( int i = 0; i < count; i++ )
    {
        objc_property_t property = propList[i];

        const char *propName = property_getName(property);
        NSString *propNameString =[NSString stringWithCString:propName encoding:NSASCIIStringEncoding];

        if(propName) 
        {
         @try {
            id value = [instance valueForKey:propNameString];
            [propPrint appendString:[NSString stringWithFormat:@"%@=%@\n", propNameString, value]];
         }
         @catch (NSException *exception) {
            [propPrint appendString:[NSString stringWithFormat:@"Can't get value for property %@ through KVO\n", propNameString]];
         }
        }
    }
    free(propList);


    // Now see if we need to map any superclasses as well.
    Class superClass = class_getSuperclass( classType );
    if ( superClass != nil && ! [superClass isEqual:[NSObject class]] )
    {
        NSString *superString = [self autoDescribe:instance classType:superClass];
        [propPrint appendString:superString];
    }

    return propPrint;
}
4

Honnêtement, le bon outil pour ce travail est le débogueur de Xcode. Il a toutes ces informations facilement accessibles de manière visuelle. Prenez le temps d'apprendre à l'utiliser, c'est un outil vraiment puissant.

Plus d'information:

tilisation du débogueur

Guide de débogage Xcode obsolète - archivé par Apple

À propos du débogage avec Xcode - archivé par Apple

À propos de LLDB et du débogage - archivé par Apple

Débogage avec GDB - archivé par Apple

Guide de débogage SpriteKit - archivé par Apple

Débogage des sujets de programmation pour Core Foundation - archivé par Apple

3
Colin Barrett

J'ai fait du cocoapod avec ça, https://github.com/neoneye/autodescribe

J'ai modifié le code de Christopher Pickslay et en ai fait une catégorie sur NSObject et j'y ai également ajouté une version plus simple. Voici comment l'utiliser:

@interface TestPerson : NSObject

@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSNumber *age;

@end

@implementation TestPerson

// empty

@end

@implementation NSObject_AutoDescribeTests

-(void)test0 {
    TestPerson *person = [TestPerson new];
    person.firstName = @"John";
    person.lastName = @"Doe";
    person.age = [NSNumber numberWithFloat:33.33];
    NSString *actual = [person autoDescribe];
    NSString *expected = @"firstName=John\nlastName=Doe\nage=33.33";
    STAssertEqualObjects(actual, expected, nil);
}

@end
1
neoneye

Je suis confus avec Introspection et Réfection avant, alors obtenez quelques informations ci-dessous.

L'introspection est la capacité d'un objet à vérifier de quel type il s'agit, ou du protocole qu'il a conformé, ou du sélecteur auquel il peut répondre. L'API objc comme isKindOfClass/isMemberOfClass/conformsToProtocol/respondsToSelector etc.

La capacité de réflexion est plus loin que l'introspection , Elle peut non seulement obtenir des informations sur les objets, mais aussi des métadonnées, des propriétés et des fonctions d'objet d'opération. tel que object_setClass peut modifier le type d'objet.

0
Jone