jrdioko
jrdioko

Reputation: 33486

Generic Objective-C description method to print ivar values

In Objective-C, it's common to override -description with a method that prints the object ID and instance variable names/values. I'd like to make a generic -description method that does this through introspection, rather than manually doing this for each class. I'd like the output to be something like:

<ClassName: 0x??????, ivar1: value1, ivar2: value2, ivar3: value3, ...>

It would also be nice to sort by instance variable name (so they are always in the same order). Finally, if this could be put into a category safely overriding the NSObject functionality, that would be perfect (but I see that's not straightforward, as NSObject.m has a warning about using -[NSString stringWithFormat:] in -description).

Upvotes: 9

Views: 4976

Answers (2)

JustSid
JustSid

Reputation: 25318

This is a great question! Because I haven't found anything like this, I wrote a small function that does this for you. It replaces - (NSString *)description of NSObject using method swizzling (yes, I'm this evil. MAHAHAHAHAHahahahaha) and prints the ivar in the order they also appear in the class (you can edit it easily to display them in alphabetical order).

DON'T! forget to call NSObjectSwizzleDescription()!

.h file:

@interface NSObject (JSObjectAdditions)
@end


void NSObjectSwizzleDescription();

.m file:

#import <objc/objc.h>
#import "JSObject.h"

@implementation NSObject (JSObjectAdditions)

- (NSString *)verboseDescription
{
    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: %p>", NSStringFromClass([self class]), self];

    uint32_t ivarCount;
    Ivar *ivars = class_copyIvarList([self class], &ivarCount);

    if(ivars)
    {
        [description appendString:@"\n{"];

        for(uint32_t i=0; i<ivarCount; i++)
        {
            Ivar ivar = ivars[i];
            const char *ivarType = ivar_getTypeEncoding(ivar);
            id ivarObject = object_getIvar(self, ivar);

            [description appendFormat:@"\n   %s: ", ivar_getName(ivar)];

            // Default signed data types
            if(strcmp(ivarType, "c") == 0)
            {
                char character = (char)ivarObject;
                [description appendFormat:@"'%c'", character];
            }
            else if(strcmp(ivarType, "i") == 0 || strcmp(ivarType, "l") == 0) // l is also 32 bit in the 64 bit runtime environment
            {
                int integer = (int)ivarObject;
                [description appendFormat:@"%i", integer];
            }
            else if(strcmp(ivarType, "s") == 0)
            {
                short shortVal = (short)ivarObject;
                [description appendFormat:@"%i", (int)shortVal];
            }
            else if(strcmp(ivarType, "q") == 0)
            {
                long long longVal = (long long)ivarObject;
                [description appendFormat:@"%l", longVal];
            }
            // Default unsigned data types
            else if(strcmp(ivarType, "C") == 0)
            {
                unsigned char chracter = (unsigned char)ivarObject;
                [description appendFormat:@"'%c'", chracter];
            }
            else if(strcmp(ivarType, "I") == 0 || strcmp(ivarType, "L") == 0)
            {
                unsigned int integer = (unsigned int)ivarObject;
                [description appendFormat:@"%u", integer];
            }
            else if(strcmp(ivarType, "S") == 0)
            {
                unsigned short shortVal = (unsigned short)ivarObject;
                [description appendFormat:@"%i", (int)shortVal];
            }
            else if(strcmp(ivarType, "Q") == 0)
            {
                unsigned long long longVal = (unsigned long long)ivarObject;
                [description appendFormat:@"%ll", longVal];
            }
            // Floats'n'stuff
            else if(strcmp(ivarType, "f") == 0)
            {
                float floatVal;
                memcpy(&floatVal, &ivarObject, sizeof(float));

                [description appendFormat:@"%f", floatVal];
            }
            else if(strcmp(ivarType, "d") == 0)
            {
                double doubleVal;
                memcpy(&doubleVal, &ivarObject, sizeof(double));

                [description appendFormat:@"%f", doubleVal];
            }
            // Boolean and pointer
            else if(strcmp(ivarType, "B") == 0)
            {
                BOOL booleanVal = (BOOL)ivarObject;
                [description appendFormat:@"%@", (booleanVal ? @"YES" : @"NO")];
            }
            else if(strcmp(ivarType, "v") == 0)
            {
                void *pointer = (void *)ivarObject;
                [description appendFormat:@"%p", pointer];
            }
            else if(strcmp(ivarType, "*") == 0 || strcmp(ivarType, ":") == 0) // SEL is just a typecast for a cstring
            {
                char *cstring = (char *)ivarObject;
                [description appendFormat:@"\"%s\"", cstring];
            }
            else if(strncmp(ivarType, "@", 1) == 0)
            {
                [description appendFormat:@"%@", ivarObject];
            }
            else if(strcmp(ivarType, "#") == 0)
            {
                Class objcClass = (Class)ivarObject;
                [description appendFormat:@"%s", class_getName(objcClass)];
            }
            else
                [description appendString:@"???"];
        }

        [description appendString:@"\n}"];
        free(ivars);
    }

    return description; 
}

@end


void NSObjectSwizzleDescription()
{
    Method origMethod = class_getInstanceMethod([NSObject class], @selector(description));
    Method newMethod = class_getInstanceMethod([NSObject class], @selector(verboseDescription));

    method_exchangeImplementations(origMethod, newMethod);
}

Upvotes: 13

Vincent Guerci
Vincent Guerci

Reputation: 14419

I don't know any code doing this, but if that's exist, I would be interested for debugging purposes!

I'm surprised I didn't find yet something like this, classic techniques just works, but it would indeed be nice to have some sort of inspect method, which is, I think, possible using reflection, but maybe I'm wrong.

I would suggest to give a look a this interesting post, which is related to what you are looking for, and could be a base to implement it.

Another interesting thing is also NSLogger, which is a nice tool for debugging.

Upvotes: 1

Related Questions