【问题标题】:Why shouldn't you use objc_msgSend() in Objective C?为什么不应该在 Objective C 中使用 objc_msgSend()?
【发布时间】:2013-06-20 05:51:53
【问题描述】:

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

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

【问题讨论】:

  • 因为它会导致我们所知的宇宙终结。从现在起 6 个月后将无法破译,尤其是在其他人正在阅读代码的情况下。
  • 因为这就像你拆开一辆山地自行车,用它的轮子和单件来移动。
  • (如果你确实使用它,请把它注释掉。)

标签: objective-c cocoa objective-c-runtime


【解决方案1】:

原因 #1:样式不好 - 多余且不可读。

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

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 的指针,等等),你的程序可能会出现错误和/或崩溃。 (你很可能会弄错,因为它甚至没有那么简单 - 并非所有返回 struct 的方法都使用这个变体,因为小型结构将适合一个或两个处理器寄存器,消除了需要使用堆栈作为返回值的位置。这就是为什么 - 除非你是一个铁杆 ABI 黑客 - 你宁愿让编译器完成它的工作,否则会有龙。)

【讨论】:

  • 重要的是要强调objc_msgSend() -- 可变参数基函数 -- 与强类型方法调用不兼容。这就是为什么在使用objc_msgSend() 时需要强制转换。这也是为什么你应该尽可能避免它的原因。一旦你转换了一个表达式,你基本上就是在告诉编译器你比它更聪明,这种情况很少发生。
  • @user529758 如果系统刷你的内存,它会更快并且只有错误。不按照您的暗示验证指针绝不是一个好主意。另外,代码是 objc_msgSend([NSObject class], @selector(alloc)), @selector(init);不是 objc_msgSend(objc_msgSend([NSObject 类], @selector(alloc)), @selector(init));但是您使用的是类方法,所以您只是否定了使用 0bjc_msgSend 的所有好处。正确的方法是“(*p_objc_msgSend)(p,mySelector);”该代码是根据 ARC 中的调用方式粘贴的。
  • 另外 #2 是如何不编程的好例子。你有指针的经验,你从来没有这样做过,也不要写 1 衬里。您验证地址存在并立即致电。您还可以使用指针和验证指针。您在 #2 中粘贴的示例对 FooBar 和 Bar 的 objc_msgSend 进行了 4 次调用。专门调用它的重点是性能,但您的示例浪费了性能优势。
【解决方案2】:

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

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

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

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

...

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

...

[100] 真的,这看起来像是msgSend 的案例,我看不到任何其他解决方案!好的,去阅读Document.m 中的TextEdit code sample from Apple。你知道他们为什么使用msgSend吗?你确定吗……再想一想……

...

[1000] 我理解 Apple 为何使用它,我的案例也类似...您已经找到并理解证明规则和您的案例匹配的例外,使用它

HTH

【讨论】:

  • 我现在必须想一想他们为什么在那段代码中直接使用objc_msgSend()。我也很惊讶它在所有权处置 (release) 之后立即被调用。也许只是这里是晚上 10 点,但实际上我并不太明白。
  • 如果您需要发送带有任意选择器和非id 参数的消息,则不能使用performSelector:…。您可以使用objc_msgSend,也可以使用NSInvocation。使用objc_msgSend 要简单得多。
  • 不幸的是,该代码很神奇。将提交错误以消除魔法,代价是增加几行可渗透的代码。
  • @bbum - 神奇?我第一次听到有人这样称呼 TextEdit ;-)
  • @robmayoff 为什么不用instanceMethodForSelector: 和一个函数指针呢?
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2011-11-07
    • 1970-01-01
    • 2019-10-24
    • 2012-02-26
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    • 2012-06-10
    • 1970-01-01
    相关资源
    最近更新 更多