24

Apple 的 Objective C 运行时指南指出,您永远不应在自己的代码中使用 objc_msgSend(),并建议使用 methodForSelector: 代替。但是,它没有为此提供任何理由。

在代码中调用 objc_msgSend() 有什么危险?

4

3 回答 3

42

原因#1:糟糕的风格——它是多余的和不可读的。

objc_msgSend()编译器在遇到 Objective-C 消息表达式时会自动生成对(或其某些变体)的调用。如果您知道要在编译时发送的类和选择器,则没有理由编写

id obj = objc_msgSend(objc_msgSend([NSObject class], @selector(alloc)), @selector(init));

代替

id obj = [[NSObject alloc] init];

即使您不知道类或选择器(甚至两者都不知道),获取正确类型的函数指针仍然更安全(至少编译器有机会警告您,如果您正在做一些可能令人讨厌/错误的事情)实现本身并改用该函数指针:

const char *(*fptr)(NSString *, SEL) = [NSString instanceMethodForSelector:@selector(UTF8String)];
const char *cstr = fptr(@"Foo");

当方法的参数类型对默认提升敏感时尤其如此 - 如果是,那么您不想通过可变参数传递它们objc_msgSend(),因为您的程序将很快调用未定义的行为。

原因2:危险且容易出错。

请注意#1 中的“或其某些变体”部分。并非所有消息发送都使用该objc_msgSend()功能本身。由于 ABI 中的复杂性和要求(特别是在函数调用约定中),存在用于返回的单独函数,例如浮点值或结构。例如,在执行某种搜索(子字符串等)的方法的情况下,它返回一个NSRange结构,取决于平台,可能需要使用 messenger 函数的结构返回版本:

NSRange retval;
objc_msgSend_stret(&retval, @"FooBar", @selector(rangeOfString:), @"Bar");

如果你弄错了(例如你使用了不合适的信使函数,你混淆了指向返回值和指向的指针self等等),你的程序很可能会出现错误和/或崩溃。(而且你很可能会弄错,因为它甚至没有那么简单 - 并非所有返回 a 的方法都struct使用这个变体,因为小型结构将适合一个或两个处理器寄存器,从而无需使用堆栈作为返回值。这就是为什么 - 除非你是一个铁杆 ABI 黑客 - 你宁愿让编译器完成它的工作,否则会有龙。)

于 2013-06-23T17:34:00.427 回答
11

你问“有什么危险?” @H2CO3 列出了一些以“除非你是铁杆 ABI 黑客”的结尾......

与许多规则一样,也有例外(在 ARC 下可能还有更多)。所以你使用的理由msgSend应该是这样的:

[1] 我认为我应该使用msgSend-不要

[2] 但我这里有一个案例... -你可能没有,继续寻找另一种解决方案

...

[10] 我真的认为我应该在这里使用它——再想一想

...

[100] 真的,这看起来像是一个案例msgSend,我看不到任何其他解决方案!好的,请阅读AppleDocument.mTextEdit 代码示例。你知道他们为什么用msgSend吗?你确定……再想想……

...

[1000] 我明白为什么 Apple 使用它,我的情况也类似......您已经找到并理解 了证明规则和您的案例匹配的异常,使用它

高温高压

于 2013-06-23T19:10:33.650 回答
4

我可以做一个案例。我们在跨平台项目(Windows、Mac 和 Linux)中的一个 C++ 文件(在我们切换到 ARC 之前)中使用了 msgSend。我们使用它来对支持的(共享代码)中的引用进行引用计数,该引用稍后用于从前端到后端,反之亦然。诚然,非常特殊的情况。

于 2013-06-25T12:31:06.513 回答