2011/07/16

Instrospection in Objective-C
Sometimes public APIs in cocoa/cocoa-touch frameworks is simply not enough or maybe a private API could save you some work.

So, this is not something new: If you read Objective-c runtime reference you will find really handy and hacky functions there.
These are some snippets:

List up methods of a class
uint methodCount = 0;
Class class = NSClassFromString(@"UIWebDocumentView");
Method *mlist = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; ++i){
    NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
}
List up protocols conformed by a class
Class class = [UITextView class];
Protocol **p1 = class_copyProtocolList(class, NULL);
for (int i = 0; p1[i]; i++) {
    printf(@"%s\n", protocol_getName(p1[i]));
}
free(p1);
List up ivars of a class
Class class = [UITextView class];
uint ivarsNum = 0;
Ivar *ivars = class_copyIvarList(class, &ivarsNum);
for (int i = 0; i < ivarsNum; i++) {
    printf(@"%s\n", ivar_getName(ivars[i]));
}
Introspect arguments of a method
Class class = [UITextView class];
SEL selector = @selector(keyboardInput:shouldInsertText:isMarkedText:);
Method method = class_getInstanceMethod(class, selector);
char *arg = method_copyArgumentType(method, 0);
printf(@"_%s_\n", arg);
free(arg);

Here you will get: @ for objects, i for integers, f for floats. That is all you get.

Using GDB In Xcode set a symbolic break point to:
-[UITextView keyboardInput:shouldInsertText:isMarkedText:]
or in gdb type:
b -[UITextView keyboardInput:shouldInsertText:isMarkedText:]
so the debugger will stop at that method. When stopped is possible to show the registers values hence is possible to inspect the arguments passed :) This is a more complete list of how to call the registers in different architectures. Since the iOS Simulator is in i386 (after prolog) I can inspect this particular method doing:
(gdb) po *(id*)($ebp + 8)
<MyTextView: 0x5911270; baseClass = UITextView; frame = (80 70; 240 323); text = 'Lorem ipsum dolor sit er ...'; clipsToBounds = YES; autoresize = RM+BM; layer = <CALayer: 0x5c0c7d0>; contentOffset: {0, 0}>

(gdb) p *(SEL*)($ebp + 12)
$1 = (SEL) 0xbd19

(gdb) po *(id*)($ebp + 16)
<UIWebDocumentView: 0xa02f000; frame = (0 0; 240 457); text = 'Lorem ipsum dolor sit er ...'; opaque = NO; userInteractionEnabled = NO; layer = <UIWebLayer: 0x5c35070>>

(gdb) po *(id*)($ebp + 20)
t

(gdb) p *(id*)($ebp + 24)
$2 = (id) 0x0
and finally to get the selector name from a SEL in gdb
(gdb) p (char*)$1
$1 = 0xbd19 "keyboardInput:shouldInsertText:isMarkedText:"

I found this super useful because now I can get the list of method of any class and dig in. Yippeee!

For example I wanted to see if I can customize some shortcuts in the iOS when using a hardware keyboard. But it turns out that the UITextView is not the one who controls that. UITextView seems to be a mere client of UIWebDocumentView which is the one who handles text input and also keyboard events. So overriding private methods of UITextView is just not enough. This is not as simple as I thought to I will save this for another post :)

Links

0 comments :