【问题标题】:iOS -- use macros to forward a bunch of messages?iOS——使用宏来转发一堆消息?
【发布时间】:2011-06-28 01:43:31
【问题描述】:

ForwardInvocation 确实存在,但它很慢并且有令人讨厌的编译器警告问题。这让我开始思考——有没有办法使用宏来快速实现一堆从另一个对象获取相关属性的 getter 方法?

例如,如果我有一个 Car 对象,它可能想要实现以下内容:

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;
}

问题——是否可以设置一些宏,以便通过在某处更改 one,我可以在头文件和实现文件中添加另一个这样的方法?

例如MagicForwardingMacro (nPistons, int, engine);

理想情况下,如果我以后想使用类似的策略从 Person 的birthCertificate 中获取一个 Person 的 firstName、lastName、placeOfBirth 和 dateOfBirth 属性,那么这些宏将是可重用的。

【问题讨论】:

  • 我不太明白你想做什么。您是否希望宏定义一个访问器方法,该方法从对象中获取值,作为参数传递给宏?
  • 我不想写标题和实现,我只想在某处写 MagicForwardingMacro(nPistons, int, engine) 并导致 -(int) nPistons;出现在标题中,并且 -(int) nPistons { return self.engine.nPistons; } 出现在实现中。
  • 在当前版本的 XCode 中无法影响宏中的多个文件。并且由于 Applescript 目前在 XCode 中被破坏,你很不走运。对不起。

标签: iphone ios macros message-forwarding


【解决方案1】:

最简单的方法可能是动态添加方法:

详述第二步:

对于每种类型,添加一个类似的方法

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

请注意,对于结构,您需要 objc_msgSend_stret,对于浮点数/双精度,您可能需要 objc_msgSend_fpret(我认为您只在 i386 上需要它;不确定 AMD64)。支持模拟器和设备的简单技巧类似于(我忘记了 GCC 使用的宏名称......)

#if __i386
#define objc_msgSend_fpret objc_msgSend
#endif

现在要实现+resolveInstanceMethod:,您需要提前知道要转发到的类。假设它是引擎。

+(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));
}

或者,有一个稍微未记录的方法-forwardingTargetForSelector: 可能足够快以满足您的需求。

编辑: 或者,您可以动态循环属性/方法。似乎没有一种内省类别的明显方法,但您可以在 protocol 中定义它们,执行@interface Engine:NSObject<Engine> ... @interface Car(DynamicEngine)<Engine> 之类的操作并使用objc_getProtocol("Engine"),然后使用protocol_copyMethodDescriptionList()/protocol_copyPropertyList()获取方法,然后添加 getter。我不确定是否将属性添加到“方法描述列表”中。另请注意,“复制”函数不会从超类复制方法/属性,这(在这种情况下)是您想要的。

【讨论】:

  • Google libextobjc 是一个优秀的公共领域库,其中包括“具体协议”——一种包含默认实现的协议形式。这确实是通过动态添加方法来实现的。
【解决方案2】:

遗憾的是,我认为 Objective-C 2.0 属性不适用于您,因为我认为您不能在属性声明中指定任何类型的转发。

您不能使用一个宏来在两个不同的位置插入文本。但是,您可以像这样使用两个宏:

//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)

如果您不介意头文件中没有显示的方法(例如,如果您只在类的实现本身中使用这些方法),您也可以单独使用实现文件宏。

这与所有者的类型无关,但它应该适用于任何表达式。

【讨论】:

    【解决方案3】:

    我正在接近我想要的。一些令人讨厌的细节仍然存在:

    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)
    

    汽车.h:

    @interface Car: SomeSuperclass {
      // some ivars
    }
    
    . . .
    
    #include CarForwarding.h
    

    汽车.m:

    . . .
    
    @implementation Car
    
    #define IMPLEMENTATION
    #include CarForwarding.h
    

    唠叨的细节:

    1) 我不喜欢#define IMPLEMENTATION 行。我希望 CarForwarding.h 以某种方式自动检测它当前是否包含在实现中。

    2) 如果我可以让转发文件中定义的内容以某种方式也以人类可读的形式出现在标题中,那就太酷了。或者更好——以某种方式将“转发”定义直接写入 Car.h 文件,所以我根本不需要 CarForwarding.h 文件。

    【讨论】: