William Jockusch
William Jockusch

Reputation: 27315

iOS -- use macros to forward a bunch of messages?

ForwardInvocation does exist, but it is slow and has the annoying problem of compiler warnings. So that got me to thinking -- is there a way to use macroes to quickly implement a bunch of getter methods that get the property in question from another object?

For example, if I have a Car object, it might want to implement the following:

Car.h:

@class SparkPlug;
@class Engine;

. . .

-(int) nPistons;
-(float) horsepower;
-(SparkPlug*) sparkPlug;

Car.m:

. . .

-(int) nPistons {
    return self.engine.nPistons;
}

-(float) horsepower {
    return self.engine.horsepower;
}

-(SparkPlug*) sparkPlug {
    return self.engine.sparkPlug;
}

Question -- would it be possible to set up some macroes so that by making one change somewhere, I could add another such method to both the header and implementation files?

e.g. MagicForwardingMacro (nPistons, int, engine);

Ideally, in such a way that the macroes would be reusable if I later wanted to later use a similar strategy to get the firstName, lastName, placeOfBirth, and dateOfBirth properties of a Person from his or her birthCertificate.

Upvotes: 1

Views: 589

Answers (3)

William Jockusch
William Jockusch

Reputation: 27315

I'm getting close to what I want. Some nagging details remain:

ForwardingInclude.h:

// no include guard; we want to be able to include this multiple times
#undef forward
#ifdef IMPLEMENTATION
#define forward(a, b, c)  -(a) b { return [[self c] b]; }
#else
#define forward(a, b, c)  -(a) b;
#endif

CarForwarding.h:

// again, no include guard
#include ForwardingInclude.h
forward(int, nPistons, engine)
forward(SparkPlug* sparkPlug, engine)

Car.h:

@interface Car: SomeSuperclass {
  // some ivars
}

. . .

#include CarForwarding.h

Car.m:

. . .

@implementation Car

#define IMPLEMENTATION
#include CarForwarding.h

The nagging details:

1) I don't like that #define IMPLEMENTATION line. I want CarForwarding.h to somehow automatically detect whether or not it is currently being included inside an implementation.

2) It would be waaaaaay cool if I could have the stuff defined in the forwarding file somehow also appear in human-readable form in the header. Or better yet -- write the "forward" definitions directly into the Car.h file somehow, so I don't need the CarForwarding.h file at all.

Upvotes: 0

tc.
tc.

Reputation: 33592

The easiest way is probably to add the methods dynamically:

Elaborating on the second step:

For each type, add a method like

-(int)getEngineInt {
  return (int()(id,SEL))(objc_msgSend)(engine, _cmd);
}

Note that for structs you need objc_msgSend_stret and for floats/doubles you might need objc_msgSend_fpret (I think you only need it on i386; not sure about AMD64). The easy hack to support both the simulator and device is something like (I forget the macro name GCC uses...)

#if __i386
#define objc_msgSend_fpret objc_msgSend
#endif

Now to implement +resolveInstanceMethod:, you need to know the class you're forwarding to ahead of time. Let's say it's Engine.

+(BOOL)instancesRespondToSelector:(SEL)name
{
  return [Engine instancesRespondToSelector:name];
}

+(BOOL)resolveInstanceMethod:(SEL)name
{
  // Do we want to super-call first or last? Who knows...
  if ([super resolveInstanceMethod:name]) { return YES; }
  // Find the return type, which determines the "template" IMP we call.
  const char * returntype = [Engine instanceMethodSignatureForSelector:name].methodReturnType;
  if (!returnType) { return NO; }

  // Get the selector corresponding to the "template" by comparing return types...
  SEL template = NULL;
  if (0 == strcmp(returntype,@encode(int))
  {
    sel = @selector(getEngineInt);
  }
  else if (0 == strcmp(Returntype,@encode(float))
  {
    ...
  }
  if (!sel) { return NO; }
  Method m = class_getInstanceMethod(self,template);
  return class_addMethod(self, name, method_getImplementation(m), method_getTypeEncoding(m));
}

Alternatively, there's a slightly undocumented method -forwardingTargetForSelector: which may be fast enough for your needs.

EDIT: Alternatively, you can loop over the properties/methods dynamically. There doesn't appear to be an obvious way to introspect categories, but you can define them in a protocol, do something like @interface Engine:NSObject<Engine> ... @interface Car(DynamicEngine)<Engine> and use objc_getProtocol("Engine") and then protocol_copyMethodDescriptionList()/protocol_copyPropertyList() to get the methods, and then add the getters. I'm not sure if properties are added to the "method description list". Also note that the "copy" functions do not copy methods/properties from superclasses, which (in this case) is what you want.

Upvotes: 1

Joe Osborn
Joe Osborn

Reputation: 1155

Sadly, I don't think Objective-C 2.0 properties will work for you because I don't think you can specify any kind of forwarding in the property declaration.

You can't have one macro that will insert text in two different places. However, you can use two macros like so:

//This could also take the third argument and discard it, if you like
#define FORWARDI(type, prop) - (type)prop;
#define FORWARDM(type, prop, owner) - (type)prop { return owner.prop; }

//In the header...
FORWARDI(float, nPistons)

//In the implementation...
FORWARDM(float, nPistons, self.engine)

If you don't mind the methods not showing up in the header file (for example, if you will only use these methods inside the class's implementation itself), you can just as well use the implementation file macro by itself.

This is agnostic to the type of the owner, but it should work with any expression.

Upvotes: 0

Related Questions