2006/12/17
Obj-C Runtime fun Redux
I got an email a few days ago asking for the code (never promise anything : you will have to deliver it eventually) so I dug in my disks to find a version of this code on a laptop harddrive that I planned to reformat...
Let me stress that this code is ultra-naïve, is a performance hog and is probably almost totally useless for anything serious. It was just a fun hack done in a few minutes, don't expect anything else. Oh, I'm might not work at all with the new Leopard Objective-2.0 runtime.
That being said, here is a quick walkthrough through the code. There's basically three parts in the hack:
- Use Objective-C reflection API to introspect class structure
- Navigate in an instance graph, processing class and instance variables name and values on the way, and keeping track visited objects.
- Dump DOT representation for the navigated instances.
Introspecting class structure might look a bit frightening: you don't have a full, clean, object-based reflection API in Objective-C similar to what you have in Java or C#, you will need to go back to runtime-level. The Apple Objective C Runtime documentation is a good reference. The interesting stuff is in #include <objc/objc-class.h>.
struct objc_class {
struct objc_class *isa;
struct objc_class *super_class;
/* ... */
struct objc_ivar_list *ivars;
};
typedef struct objc_class *Class;
struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
};
Cool. Everything we need to know about. Obtaining the struct objc_class from an Objective-C instance is straightforward: [myObject class].
Then we just have to iterate:
- (NSArray*)getFieldsForClass:(Class)klass
{
NSMutableArray* array = [NSMutableArray array];
int i;
Ivar ivar;
struct objc_ivar_list* ivarList = klass->ivars;
if (ivarList!= NULL && (ivarList->ivar_count>0)) {
for ( i = 0; i < ivarList->ivar_count; ++i ) {
rtIvar = (ivarList->ivar_list + i);
// Interesting stuff:
// ivar->ivar_type, ivar->ivar_name, ivar->ivar_offset
// Store this info in a OCVField object, add it to array
}
}
if (klass->super_class!=0)
[array addObjectsFromArray:[self getFieldsForClass:klass->super_class]];
return array;
}
The ivar_type is a string, a type encoding representing the ivar type. You might have encountered it with Foundation serialization. With the offset, we know where to peek from the instance pointer to get ivar value.
When you know the ivar name, you can also use the runtime function class_getInstanceVariable.
Then, browsing an instance graph is not that hard:
-(void)processFieldsForObject:(id)obj fields:(NSArray*)f toString:(NSMutableString*)s
{
OCVField* field;
NSEnumerator* e = [f objectEnumerator];
while (field = [e nextObject])
{
if (![field isPrimitive]) {
NSString* name = [field getName];
id ref = [field getValueForObject:obj];
if (ref==nil) {
// A link to a new "nil" node in the graphical reprensentation
// Why the special case ? we don't want a single nil node with
// hundred of links pointing to it.
}
else
{
// A link from obj to the 'ref' ivar value
[self appendGraphvizRepresentationFor:ref toString:s];
}
}
}
}
-(void)appendGraphvizRepresentationFor:(id)obj toString:(NSMutableString*)s
{
if (obj==nil)
obj = [NSNull null];
if ([visited containsObject:obj])
return;
[visited addObject:obj];
[obj appendDotRepresentationToString:s withContext:self];
if ([obj processFields])
[self processFieldsForObject:obj fields:[obj getFields] toString:s];
}
The only missing part is producing GraphViz Dot syntax. Objective-C categories are a nice way to implement that: we add methods to existing class, starting with NSObject to specify the Dot representation for an instance with the given type, and to authorize or not further ivar processing for the instance.
For example, for NSArray and NSDictionary, we'll have a custom representation, and for a NSString, we just want to output the "description" (e.g. the value) of the string instance:
@implementation NSString (DotRepresentation)
-(BOOL)processFields
{
return NO;
}
-(void)appendDotRepresentationToString:(NSMutableString*)s withContext:(OCVContext*)c
{
[s appendFormat:@"%@ [label=\"@\\\"%@\\\"\"];\n",[self dotName],[self description]];
}
@end
Et voilà: the dot file (for GraphViz, check out the great OS X version), or pdf rendering

Source code, BSD License. Please check the first post for additional notes.
Thanks Michael for the motivation !
<< Home
