使用objc_msgSend而不使用新运行时强制转换的替代方法

时间:2015-02-19 16:51:00

标签: ios objective-c objective-c-runtime scripting-bridge

我正在编写一个objc桥,我找到了一种使用objc_msgSend调用objc方法的非常有效的方法。

基本上,代码能够生成一个宏,该宏传递给objc_msg,从NSArray传递正确数量的参数(需要metamacros.h)。

#import "metamacros.h"

#define CFIEXTRACTARGS(COUNT, ARR) \
, ARR[COUNT] \

#define objc_call(RECIEVER, SELECTOR, COUNT, ARR) \
objc_msgSend(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
) \

一切都按预期运行并且非常好但不幸的是我刚刚发现由于最近运行时的Apple更改,如果没有显式强制转换为正确的函数指针,则无法直接调用objc_msgSend。

来自:https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html :“虽然消息函数的原型具有可变形式,但Objective-C运行时调用的方法函数不共享相同的原型.Object-C运行时直接调度到实现该方法的函数,因此如前所述,调用约定不匹配。因此,必须将objc_msgSend函数强制转换为与被调用的方法函数匹配的原型。“

示例:

- (int) doSomething:(int) x { ... }
- (void) doSomethingElse {
    int (*action)(id, SEL, int) = (int (*)(id, SEL, int)) objc_msgSend;
    action(self, @selector(doSomething:), 0);
}

显然我不能为每个功能组合创建一个演员,所以我想知道什么是有效的替代方案。 NSInvocation实在太慢了。

1 个答案:

答案 0 :(得分:1)

有点困惑,因为使用NSArray作为参数肯定意味着它们必须都是对象类型?

然而,除了这个问题之外,没有必要停止使用基于宏的方法,只需添加转换作为参数:

#define objc_call(RECIEVER, TYPE, SELECTOR, COUNT, ARR) \
(TYPE objc_msgSend)(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
)

使用如下:

- (NSString *) doSomething:(NSString *) x { ... }

(注意使用NSString - 对象类型,与问题中的int不同)

- (void) doSomethingElseToo
{
   NSArray *args = @[ @"too" ];
   NSString *res = objc_call(self, (NSString * (*)(id, SEL, NSString *)), @selector(doSomething:), 1, args);
   NSLog(@"res = %@", res);
}

HTH

<强>附录

@ RichardJ.RossIII在评论中建议他相信你希望避免提供演员阵容;并不是说你想避免为不同的类型设置不同的宏,我认为是这种情况并提供了上述解决方案。

鉴于您希望避免NSInvocation让我们再次查看您的宏。如前所述,它使用NSArray从中提取参数,因此每个参数都是一个对象,即类型id。如果所有参数都属于同一类型,则可能的强制转换数量会急剧减少。

我们考虑过这些争论,您希望支持多少种返回类型?我们暂时假设它是一,id。以下两个宏将完成最多19个参数:

#define metamacro_cast(COUNT) \
metamacro_at(COUNT, \
(id (*)(id, SEL)), \
(id (*)(id, SEL, id)), \
(id (*)(id, SEL, id, id)), \
(id (*)(id, SEL, id, id, id)), \
... // repeat until there are 19 id's
)

#define objc_call2(RECIEVER, SELECTOR, COUNT, ARR) \
(metamacro_cast(COUNT) objc_msgSend)(RECIEVER, SELECTOR \
metamacro_for_cxt(COUNT, CFIEXTRACTARGS,, ARR) \
)

使用此宏与您的完全相同,无需额外参数:

- (void) doSomethingElseThree
{
   NSArray *args = @[ @"three" ];
   NSString *res = objc_call2(self, @selector(doSomething:), 1, args);
   NSLog(@"res = %@", res);
}

如果您希望支持多种返回类型,则需要多个objc_call_returning_type宏;或写一个只接受返回类型的宏,不像我的第一个接受整个演员,并尝试使用metamacros.h更多的宏体操。