为什么不在Objective C中使用objc_msgSend()?

cfi*_*her 21 cocoa objective-c objective-c-runtime

Apple的Objective C Runtime Guide指出,不要在自己的代码中使用objc_msgSend(),而是建议使用methodForSelector:.但是,它没有提供任何理由.

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

小智 37

理由#1:糟糕的风格 - 它是多余的,不可读的.

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

id obj = objc_msgSend(objc_msgSend([NSObject class], @selector(alloc)), @selector(init));
Run Code Online (Sandbox Code Playgroud)

代替

id obj = [[NSObject alloc] init];
Run Code Online (Sandbox Code Playgroud)

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

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

当方法的参数类型对默认促销敏感时尤其如此 - 如果它们是,那么您不希望通过可变参数objc_msgSend()获取传递它们,因为您的程序将快速调用未定义的行为.

理由#2:危险且容易出错.

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

NSRange retval;
objc_msgSend_stret(&retval, @"FooBar", @selector(rangeOfString:), @"Bar");
Run Code Online (Sandbox Code Playgroud)

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

  • 重要的是要强调`objc_msgSend()` - varargs基函数 - 与强类型方法调用不兼容.这就是使用`objc_msgSend()`时需要****的原因.这也正是为什么你应该尽可能地避免它.一旦你转换表达式,你基本上告诉编译器你比它更聪明,这种情况很少发生. (10认同)

CRD*_*CRD 10

你问"危险是什么?" 并且@ H2CO3列出了一些结尾,"除非你是一个核心的ABI黑客"......

与许多规则一样,也有例外(在ARC下可能还有一些例外).因此,您使用的推理msgSend应该遵循以下方面:

[ 1] I think I should use msgSend - don't.

[2] But I've a case here... - you probably haven't, keep looking for another solution.

...

[10] I really think I should use it here - think again.

...

[100] Really, this looks like a case for msgSend, I can't see any other solution! OK, go read Document.m in the TextEdit code sample from Apple. Do you know why they used msgSend? Are you sure... think again...

...

[1000] I understand why Apple used it, and my case is similar... You've found and understood the exception that proves the rule and your case matches, use it!

HTH

  • 我现在要考虑为什么他们直接在那段代码中使用`objc_msgSend()`.我也很惊讶它是在*处置所有权(`release`)后立即调用的.也许它只是在这里的晚上10点,但实际上我并没有真正得到它. (2认同)
  • 如果需要发送带有任意选择器和非```参数的消息,则不能使用`performSelector:...`.你可以使用`objc_msgSend`或者你可以使用`NSInvocation`.使用`objc_msgSend`要简单得多. (2认同)