ios - 在新的运行时中使用 objc_msgSend 而无需强制转换的替代方法

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

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

基本上,代码能够生成一个宏,该宏传递给 objc_msgSend 从 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 :“虽然消息函数的原型(prototype)具有可变形式,但 Objective-C 运行时调用的方法函数并不共享相同的原型(prototype)。Objective-C 运行时直接调度到实现该方法的函数,因此如前所述,调用约定不匹配。因此,您必须将 objc_msgSend 函数转换为与被调用的方法函数匹配的原型(prototype)。”

例子:

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

显然我不能为每个函数组合创建一个强制转换,所以我想知道什么是有效的替代方法。 NSInvocation 真的太慢了​​。

最佳答案

有点困惑,因为使用 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 尝试更多的宏体操。

关于ios - 在新的运行时中使用 objc_msgSend 而无需强制转换的替代方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28612101/

相关文章:

objective-c - 将音频路由到 OS X 上的特定设备输出

iphone - 检查文本字段文本是否几乎等于字符串

ios - 为什么 methodLists 是指向 objc_class 中的指针的指针

ios - 如何在运行时确定只读属性是否弱?

objective-c - UIKit 添加 : bridging the gap between eg CGRectValue and NSStringFromCGRect?

ios - 如何在 iOS 版 MonoTouch/Xamarin 中导航命名空间?

ios - 我不能不隐藏键盘

ios - 无法在TableView中查看图像

iOS AppDelegate didReceiveRemoteNotification 未在后台通过蜂窝网络静默推送触发

objective-c - 获取 Cocoa 中 NSMenuItem 的默认字体名称?