Message Forwarding

来源:互联网 发布:新浪微博域名二次修改 编辑:程序博客网 时间:2024/05/16 09:44

Message Forwarding

Objective-C object messaging finds and executes a method on an object based on the message it receives. The object type can either be specified in the code and statically bound at compile time (static typing) or be unspecified with its type resolved at runtime (dynamic typing). In either case, at runtime, the receiving object interprets the message to determine which method to invoke. This runtime resolution of method calls makes it easy to change and/or extend programs dynamically, but also carries with it a certain risk: it permits a program to send a message to an object that may not have a corresponding method attached to it. Under the default scenario, if this happens, a runtime exception is thrown. However, Objective-C provides another option: through a mechanism called message forwarding, it is possible to configure an object to perform user-defined processing when it receives a message not mapped to its set of methods. Message forwarding enables an object to perform a variety of logic on any unrecognized message it receives, perhaps parceling it out to a different receiver who can respond to the message, sending any unrecognized messages to the same destination, or simply silently “swallowing” the message (i.e., performing no processing nor causing a runtime error to be thrown).

Forwarding Options

Objective-C provides two types of message forwarding options that you can use.

  • Fast forwarding: Classes that descend fromNSObject can implement fast forwarding by overriding the NSObject forwardingTargetForSelector: method to forward the method to another object. This technique makes it appear like the implementations of your object and the forwarding object are combined. This simulates the behavior of multiple inheritance of class implementations. It works well if you have a target class that defines all the possible messages that your object can consume.
  • Normal (full) forwarding: Classes that descend from NSObject can implement normal forwarding by overriding the NSObject forwardInvocation: method. This technique enables your object to use the full contents of the message (target, method name, parameters).

Fast forwarding works well if you have a target class that defines all the possible messages that your object can consume. Full fowarding should be used if you don’t have such a target class or you would like to perform other processing on message receipt (for example, just logging and swallowing the message).


Message Forwarding Helper Class

First, you’ll create the helper class. In Xcode, create a new Objective-C class. Name it HydrogenHelper and make the Elements project its target. In the Xcode navigator pane, observe that two new files (HydrogenHelper.h andHydrogenHelper.m) have been created. Select theHydrogenHelper.h file, and then in the editor pane, update the interface as shown in Listing 3-9.

Listing 3-9.   HydrogenHelper Interface

#import <Foundation/Foundation.h>@interface HydrogenHelper : NSObject- (NSString *) factoid;@end

This interface declares a single instance method, factoid, which returns a pointer to an NSString (NSString *). Next, select the implementation file (HydrogenHelper.m) and define the factoid method as shown in Listing 3-10.

Listing 3-10.   HydrogenHelper Implementation

#import "HydrogenHelper.h"@implementation HydrogenHelper- (NSString *) factoid{  return @"The lightest element and most abundant chemical substance.";}

As you can see from Listing 3-10, the HydrogenHelper factoid method returns a simple fact about the Hydrogen element. Now you are going to update the Hydrogen class to support fast forwarding. In the Hydrogen implementation (Hydrogen.m), add the following code to overwrite the default implementation of the forwardingTargetForSelector:method, as shown in Listing 3-11 (updates are shown in bold).

Listing 3-11.   Hydrogen Class Message Fast Forwarding Updates

@implementation Hydrogen{@private HydrogenHelper *helper;}...- (id) initWithNeutrons:(NSUInteger)neutrons{  if ((self = [super init]))  {    // Initialization code here.    _chemicalElement = @"Hydrogen";    _atomicSymbol = @"H";    _protons = 1;    _neutrons = neutrons;         // Create helper for message forwarding    helper = [[HydrogenHelper alloc] init];  }     return self;}- (id) forwardingTargetForSelector:(SEL)aSelector{  if ([helper respondsToSelector:aSelector])  {    return helper;  }  return nil;}...@end

First, the Hydrogen class adds a HydrogenHelper* instance variable. Next, in the init method, the HydrogenHelper object is created and initialized. This is the target object that will be used for message forwarding. Finally, theforwardingTargetForSelector: method is implemented. The method first checks to see if the message is one that the target (HydrogenHelper) object can process. If it can, it returns that object; otherwise, it returns nil. Recall that theHydrogenHelper class has a single instance method, factoid. Thus if a Hydrogen object receives a message for an instance method named factoid, it will be redirected to send that message to its HydrogenHelper object (see Figure 3-4).

9781430250500_Fig03-04.jpg

Figure 3-4. Fast forwarding the factoid method for the Hydrogen class

Great! You have implemented fast forwarding for the Hydrogenclass. Now let’s test the class using fast forwarding. Select themain.m file and update the main() function, as shown inListing 3-12.

Listing 3-12.   Testing Fast Forwarding of the Hydrogen Class

int main(int argc, const char * argv[]){  @autoreleasepool  {    Atom *atom = [Hydrogen hydrogenWithNeutrons:0];    [atom logInfo];    id atom1 = [[Hydrogen alloc] initWithNeutrons:1];    [atom1 logInfo];    // Use message forwarding to get a fact about Hydrogen    Hydrogen *atom2 = [Hydrogen hydrogenWithNeutrons:2];    NSString *fact = [atom2 factoid];    [atom2 logInfo:fact];  }     return 0;}

On the left side of the editor pane, you’ll probably see anexclamation point in a red circle located on the line NSString *fact = [atom2 factoid]. If you click this exclamation point, you will see an error message (see Figure 3-5).

9781430250500_Fig03-05.jpg

Figure 3-5. Hydrogen class object messaging error

This error, “No visible @interface for ‘Hydrogen’ declares the selector ‘factoid’,” occurs because the Hydrogen class does not have a factoid instance method declared in its interface. The compiler needs to know the full method signature of every message that a program can possibly send—even those that are forwardedThe solution is to declare the unknown method, either in a class interface or in a category. In Xcode, create a new Objective-C category, enter Helper for the name, and select Atom in the category drop-down list. Make the Elements project its target and the Elements folder the location where the files will be saved. In the Xcode project navigator pane, two new files have been added to the Elements folder:Atom+Helper.h and Atom+Helper.m. These files are the category interface and implementation files. Because you don’t need the implementation, delete the Atom+Helper.m file by selecting it in the navigator pane and then selecting Deletefrom the Xcode Edit menu. Next, in the Atom+Helper.h header file, update the category interface as shown in Listing 3-13.

Listing 3-13.   Atom Helper Category Interface

#import "Atom.h"@interface Atom (Helper)- (NSString *) factoid;@end

In Listing 3-13, the factoid method has been added to the category interface. Since the Hydrogen class is a subclass of the Atom class, the compiler will now see the factoid method, thereby resolving the error. The main.m file should include theAtom helper category interface in its list of imports (see Listing 3-14).

Listing 3-14.   Imports for main() function

#import <Foundation/Foundation.h>#import "Atom.h"#import "Atom+Nuclear.h"#import "Atom+Helper.h"#import "Hydrogen.h"int main(int argc, const char * argv[]){  ...}

If you compile and run the project, the output should be as shown in Figure 3-6.

9781430250500_Fig03-06.jpg