我一直在分析我的回合制游戏应用程序,并且遇到了一个有趣的(也许)问题。如下图所示,它似乎objc_msgSend占用了我的应用程序运行时间的近一分钟。这是什么?这是代码写得不好的标志吗?谢谢!
正如上面@user1118321所说,objc_msgSend基本上就是Objective-C的消息分发的实现。基本上,当您发送诸如 之类的消息时[foo bar],objc_msgSend会被调用,它实际上会执行以下操作:
弄清楚foo是什么类。
将bar消息(转换为基于字符串的选择器)发送到foo,并获取一个实现(基本上是一个 C 函数,它将foo和 选择器作为其前两个参数)。这可以在运行时拦截并以许多粗糙的方式进行操作,这非常酷,但也会产生性能成本。此外,选择器操作涉及字符串操作,这会带来自身的性能成本。
从步骤 2 调用实现。
如果您想深入了解 内部工作原理objc_msgSend,这篇文章非常棒:https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html
不管怎样,所有这些显然不会像直接的 C 函数调用那么快,但在大多数情况下,开销objc_msgSend不足以成为一个主要问题(函数本身是用一些相当硬核的语言编写的)手工优化的汇编程序并使用了一堆缓存技术,因此它可能非常接近它可以提供的所有功能的最佳性能)。然而,在某些情况下,objc_msgSend的性能可能是一个问题,对于这些情况,您可以尝试尽量减少 Objective-C 调用的使用,如 @user1118321 所说。或者,如果您可以缩小导致问题的特定 Objective-C 方法调用的范围,则可以使用一种称为 IMP 缓存的技术,通过该技术您可以查找一次方法并将其实现保存为 C 函数指针,这样然后,您可以根据需要重复调用,发送对象和选择器作为前两个参数。
例如,如果您有这个对象:
@interface Foo: NSObject
- (id)doSomethingWithString:(NSString *)bar;
@end
Run Code Online (Sandbox Code Playgroud)
你可以IMP这样得到它:
Foo *foo = ...
SEL selector = @selector(doSomethingWithString:);
IMP imp = [foo methodForSelector:selector];
id (*funcVersion)(Foo *, SEL, NSString *) = (id (*)(Foo *, SEL, NSString *))imp;
Run Code Online (Sandbox Code Playgroud)
您可以将funcVersion选择器存储在某处,然后像这样调用它。您不会承担这样做的成本objc_msgSend,因为您将有效地跳过上面列表中的前两个步骤。
id returnValue = funcVersion(foo, selector, @"Baz");
Run Code Online (Sandbox Code Playgroud)
objc_msgSend是一个将选择器转换为地址并在您调用 Objective-C 中的方法时跳转到该地址的函数。这并不表明编程本身很差。但是,如果它占用了程序的大部分时间,您可能需要考虑重构代码,以便不需要调用这么多方法来完成您正在做的工作。如果没有有关您的应用程序的更多信息,就不可能告诉您如何继续。
我以前也打过这个。就我而言,我的应用程序调用了一种方法来检索NSDictionary. 但事实证明,字典在应用程序的整个生命周期中都是静态的。每次我调用该方法时,都会从头开始创建它。因此,我没有重复调用创建字典的方法,而是在开始时调用它一次并保存(保留)结果,从而消除了以后对该方法的所有调用。