Forwarding Messages
The solution to implementing this type of messaging is the forwarding mechanism in Objective-C. When you send a message to an object that it doesn't understand, the runtime system invokes some fallback code. This is quite complex under the hood, but most of that complexity is hidden from you by NSObject. If you're interested in how it works, take a look at the GSFFIInvocation class in GNUstep. I advise you to have a glass of brandy ready to help you recover.
To handle messages that don't have an associated method, you need to implement two methods:
- -methodSignatureForSelector: gets information about the arguments. This method is used to get the information required to deconstruct the stack frame and put the arguments in an NSInvocation object.
- -forwardInvocation: receives the message wrapped up in an NSInvocation object and is responsible for handling it.
To implement a map method, we need to return a proxy object that does this kind of forwarding. We can do so very simply — by implementing a category on NSArray that does this.
@implementation NSArray (Map) - (id) map { return [[[HOMArrayMapProxy alloc] initWithArray: self] autorelease]; } @end
The proxy itself should implement the -initWithArray: method in order to keep a reference to the array. It should also implement a -forwardInvocation: method looking something like this:
- (void)forwardInvocation: (NSInvocation*)anInvocation { SEL selector = [anInvocation selector]; NSMutableArray *mappedArray = [NSMutableArray array]; for (object in array) { if ([object respondsToSelector: selector]) { [anInvocation invokeWithTarget: object]; id mapped; [anInvocation getReturnValue: &mapped]; [mappedArray addObject: mapped]; } } [anInvocation setReturnValue: mappedArray]; }
This method iterates over every element in the array, using fast enumeration, and sends the message to each object. Notice that using the invocation is a two-step process. We need to provide space — the mapped variable — for it to store the return value, and then pass it that address to get the result out. Once we're finished, we return the mappedarray by setting the result of the invocation.
For a complete implementation, you need to add a corresponding -methodSignatureForSelector: method, one which iterates over the array and returns the result of sending -methodSignatureForSelector: to the first object it finds responding to the selector.
That's all you need. At first glance it looks quite complicated, but a lot of it is fairly generic code that can be reused; implementing other operations on collections, such as fold, is almost identical.