Forwarding NSInvocations in Objective-C
Even more fun with dynamic messages! Last time, we took a first look at the NSInvocation class, and how it can be used to dynamically construct calls to abitrary objects. This time, we're going to take a look at NSObject's -forwardInvocation: method, which allows us to capture a message and retarget then resend it.By default, if you try to call a method that doesn't exist, an exception is raised. The exception looks something like this in the console:
*** -[MiniObject stringForDate:usingFormatter:]: selector not recognized [self = 0x39a2d0]
We can intercept this call before it generates an exception. To do this, we have to override two NSObject methods. Here's our little sample class that does just that:
@interface MiniObject : NSObject
{
id delegate;
}
@end
@implementation MiniObject
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
// does the delegate respond to this selector?
if ([delegate respondsToSelector:selector])
{
// yes, return the delegate's method signature
return [delegate methodSignatureForSelector:selector];
} else {
// no, return whatever NSObject would return
return [super methodSignatureForSelector: selector];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
// we could get fancier here, but we already did
// a basic 'responds to' check before
NSLog(@"Forwarding invocation from %@ to %@", self, delegate);
[invocation invokeWithTarget:delegate];
}
@end
Now we need to actually use this in our Controller:
@implementation Controller
- (IBAction)click:(id)sender
{
// create an instance of MiniObject
MiniObject * mini = [[MiniObject alloc] init];
// set the delegate value to 'self' using KVC.
// using KVC means no need for -setDelegate:
[mini setValue:self forKey:@"delegate"];
// create some arguments to pass in
NSDate * myDate = [NSDate date];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
// this will generate a warning, the Objective-C
// compiler doesn't make it an error
NSString * result;
NSLog(@"Sending message to %@", mini);
result = [mini stringForDate:myDate usingFormatter:formatter];
NSLog(@"The result is: %@", result);
[formatter release];
[mini release];
}
- (NSString *) stringForDate: (NSDate *)date
usingFormatter: (NSDateFormatter *)formatter
{
// yes, this doesn't do anything interesting.
// just using as a simple example
NSLog(@"Returning value from %@", self);
return [formatter stringFromDate: date];
}
@end
The magic happens on this line:
result = [mini stringForDate:myDate usingFormatter:formatter];
In other languages, this line would not compile because the MiniObject class doesn't implement this method. The Objective-C compiler just marks it as a warning and trusts that things can work out at runtime. Here's what the console says when this thing runs:
Sending message to <MiniObject: 0x35a290>
Forwarding invocation from <MiniObject: 0x35a290> to <Controller: 0x301340>
Returning value from <Controller: 0x301340>
The result is: 05/13/06
So we start by sending the the message to the 'mini' object, which is an instance of MiniObject, but we intercept the call and retarget it at the delegate, which is an instance of Controller.
Of course, the guts of forwardInvocation: and methodSignatureForSelector: could do more if necessary. These are just simple demo implementations.
Forwarding NSInvocations in Objective-C
Posted May 13, 2006 — 7 comments below
Posted May 13, 2006 — 7 comments below
hitoro — May 14, 06 1226
Smalltalk even goes one step further: the default implementation of the #doesNotUnderstand: method opens a debug window from where the missing method can be implemented and executed without even breaking the current application flow. There is a nice video showing how to use this feature in a Smalltalk IDE.
Jeroen Leenarts — May 14, 06 1227
Same trick... Plus some context of why it would be usefull. When you start working with NSUndoManager it is vital to understand what NSInvocations are and how they work.
Keep on hacking scott.. Nice to see some code again on your blog.
kinn — May 14, 06 1228
Scott Stevenson — May 14, 06 1230
Ruby is another nice dynamic language. I'm definitely a fan.
Chris — May 14, 06 1231
Erik — May 17, 06 1258
forwardInvocation:
needs to be implemented, because it's doing the work of forwarding the method invocation to the delegate, but I didn't quite understand the purpose of implementingmethodSignatureForSelector:
. Obviously I'm an ObjC noob so go easy.Ken — May 21, 06 1308
However, if a class is forwarding a message that it doesn't itself implement, the runtime only knows the name, not the types. So, it has to ask the class doing the forwarding for the method signature in order to have all the information it needs.