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.
Design Element
Forwarding NSInvocations in Objective-C
Posted May 13, 2006 — 7 comments below




 

hitoro — May 14, 06 1226

The capability to catch and forward unknown messages is also available in Ruby (method_missing) and Smalltalk (#doesNotUnderstand:).

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

See page 140 of Aaron Hillegas's Cocoa Programming for Mac OS X.

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

Nice comparative hints, hitoro! Yes, very often comparing is seeing as when you scan through www.rubycocoa.com (for example)!

Scott Stevenson — May 14, 06 1230 Scotty the Leopard

The capability to catch and forward unknown messages is also available in Ruby

Ruby is another nice dynamic language. I'm definitely a fan.

Chris — May 14, 06 1231

Using Ruby is sheer bliss; it makes me pine for instance variables in ObjC categories. And blocks in Objective-C.

Erik — May 17, 06 1258

So, why do both methods (in the MiniObject class) need to exist? I can understand why 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 implementing methodSignatureForSelector:. Obviously I'm an ObjC noob so go easy.

Ken — May 21, 06 1308

Erik, an NSInvocation encapsulates the method signature, not just the selector. A selector is just the name of a message, but doesn't include the types of the arguments and the return value. A signature does include those types.

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.




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.





Copyright © Scott Stevenson 2004-2015