Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

One More Thing: Message Forwarding

Although Objective-C does not provide true multiple-inheritance, it offers a work-around that lets objects respond to messages that are implemented in other classes. If you want your object to respond to another class’s messages, you can add message forwarding to your applications and gain access to that object’s methods.

Normally, sending an unrecognized message produces a runtime error, causing an application to crash. But before the crash happens, iOS’s runtime system gives each object a second chance to handle a message. Catching that message lets you redirect it to an object that understands and can respond to that message.

Consider the Car example used throughout this chapter. The carInfo property introduced midway through the examples returns a string that describes the car’s make, model, and year. Now imagine if a Car instance could respond to NSString messages by passing them to that property. Send length to a Car object and instead of crashing, the object would return the length of the carInfo string. Send stringByAppendingString: and the object adds that string to the property string. It would be as if the Car class inherited (or at least borrowed) the complete suite of string behavior.

Objective-C provides this functionality through a process called “message forwarding.” When you send a message to an object that cannot handle that selector, the selector gets forwarded to a forwardInvocation: method. The object sent with this message—namely an NSInvocation instance—stores the original selector and arguments that were requested. You can override forwardInvocation: and send that message on to another object.

Implementing Message Forwarding

To add message forwarding to your program, you must override two methods—namely, methodSignatureForSelector: and forwardInvocation:. The former creates a valid method signature for messages implemented by another class. The latter forwards the selector to an object that actually implements that message.

Building a Method Signature

This first method returns a method signature for the requested selector. For this example, a Car instance cannot properly create a signature for a selector implemented by another class (in this case, NSString). Adding a check for a malformed signature (that is, returning nil) gives this method the opportunity to iterate through each pseudo-inheritance and attempt to build a valid result. This example draws methods from just one other class via self.carInfo:

- (NSMethodSignature*) methodSignatureForSelector:(SEL)selector
{
    // Check if car can handle the message
    NSMethodSignature* signature = [super
        methodSignatureForSelector:selector];

    // If not, can the car info string handle the message?
    if (!signature)
        signature = [self.carInfo methodSignatureForSelector:selector];

    return signature;
}

Forwarding

The second method you need to override is forwardInvocation:. This method only gets called when an object has been unable to handle a message. This method gives the object a second chance, allowing it to redirect that message. The method checks to see whether the self.carInfo string responds to the selector. If it does respond, it tells the invocation to invoke itself using that object as its receiver.

- (void)forwardInvocation:(NSInvocation *)invocation
{
    SEL selector = [invocation selector];

    if ([self.carInfo respondsToSelector:selector])
    {
        printf("[forwarding from %s to %s] ", [[[self class] description]
            UTF8String], [[NSString description] UTF8String]);
        [invocation invokeWithTarget:self.carInfo];
    }
}

Using Forwarded Messages

Calling nonclass messages such as UTF8String and length produces compile-time warnings, which you can ignore. The code shown in Figure 3-4 causes two compiler warnings. The code, however, compiles and (more importantly) runs without error. As the figure shows, you can send Car instance methods that are defined by the class itself and also those implemented by NSString.

Figure 3-4

Figure 3-4. The compiler issues warnings for forwarded methods, but the code runs without error.

House Cleaning

Although invocation forwarding mimics multiple inheritance, NSObject never confuses the two. Methods such as respondsToSelector: and isKindOfClass: only look at the inheritance hierarchy and not at the forwarding change.

A couple of optional methods allow your class to better express its message compliance to other classes. Reimplementing respondsToSelector: and isKindOfClass: lets other classes query your class. In return, the class announces that it responds to all string methods (in addition to its own) and that it is a “kind of” string, further emphasizing the pseudo-multiple inheritance approach.

// Extend selector compliance
- (BOOL)respondsToSelector:(SEL)aSelector
{
    // Car class can handle the message
    if ( [super respondsToSelector:aSelector] )
        return YES;

    // CarInfo string can handle the message
    if ([self.carInfo respondsToSelector:aSelector])
        return YES;

    // Otherwise...
    return NO;
}

// Allow posing as class
- (BOOL)isKindOfClass:(Class)aClass
{
    // Check for Car
    if (aClass == [Car class]) return YES;
    if ([super isKindOfClass:aClass]) return YES;

    // Check for NSString
    if ([self.carInfo isKindOfClass:aClass]) return YES;

    return NO;
}

Super-easy Forwarding

The method signature/forward invocation pair of methods provides a robust and approved way to add forwarding to your classes. A simpler approach is also available on iOS 4.0 and later devices. You can replace both those methods with this single one, which does all the same work with less coding and less operational expense. According to Apple, this approach is “an order of magnitude faster than regular forwarding.”

- (id)forwardingTargetForSelector:(SEL)sel
{
    if ([self.carInfo respondsToSelector:sel]) return self.carInfo;
    return nil;
}
  • + Share This
  • 🔖 Save To Your Account