iOS:Runtime - objc_msgSend函数

2021-03-17  本文已影响0人  Recorder_MZou

笔记记录:来源于apple的文档,具体参考:apple文档

消息传递

本章介绍如何将消息表达式转换objc_msgSend函数调用,以及如何按名称引用方法。然后,它说明了如何利用objc_msgSend,以及(如果需要)如何规避动态绑定。

objc_msgSend函数

在Objective-C中,消息直到运行时才绑定到方法实现。编译器转换一个消息表达式,

[receiver message]

调用消息传递功能, objc_msgSend。此功能需要接收器 消息中提到的方法的名称(即方法选择器)作为其两个主要参数:

objc_msgSend(receiver, selector)

所有的属性也都是通过objc_msgSend进行转发的:

objc_msgSend(receiver, selector, arg1, arg2, ...)

消息传递功能完成了动态绑定所需的一切:

注意: 编译器会生成对消息传递功能的调用。您永远不需要在编写的代码中直接调用它。

消息传递的关键在于编译器为每个类和对象构建的结构。每个类结构都包含以下两个基本元素:

这些类和对象结构的元素如图3-1所示。
图3-1 消息传递框架


image.png

当消息发送到对象时,消息传递功能将跟随对象的 isa指向类结构的指针,该类结构在调度表中查找方法选择器。如果无法在其中找到选择器,则objc_msgSend跟随指向超类的指针,并尝试在其调度表中找到选择器。连续的失败导致objc_msgSend爬升类层次结构,直到到达NSObject类。找到选择器后,该函数将调用在表中输入的方法,并将该方法传递给接收对象的数据结构。

这是在运行时选择方法实现的方式,或者,在面向对象编程的术语中,方法是动态绑定到消息的。

为了加快消息传递过程,运行时系统会在使用方法的选择器和地址时对其进行缓存。每个类都有一个单独的缓存,并且可以包含继承的方法以及该类中定义的方法的选择器。在搜索调度表之前,消息传递例程首先检查接收对象的类的缓存(根据理论,曾经使用过的方法可能会再次使用)。如果方法选择器在缓存中,则消息传递仅比函数调用慢一点。一旦程序运行了足够长的时间以“预热”其缓存,几乎它发送的所有消息都将找到一个缓存方法。缓存在程序运行时动态增长以容纳新消息。

使用隐藏参数

objc_msgSend找到实现方法的过程时,它将调用该过程并将消息中的所有参数传递给该过程。它还向过程传递两个隐藏参数:

这些参数为每个方法实现提供了有关调用它的消息表达式的两半的明确信息。之所以说它们是“隐藏的”,是因为它们没有在定义该方法的源代码中声明。在编译代码时将它们插入到实现中。
尽管未明确声明这些参数,但是源代码仍然可以引用它们(就像可以引用接收对象的实例变量一样)。方法将接收对象称为self,并将其作为自己的选择器 _cmd。在下面的示例中,_cmd引用strange方法的选择器和self接收strange消息的对象

- (void)strange {
    id target = getTheReceiver();
    SEL method = getTheMethod();
    
    if ( target == self  || method == _cmd) {
        return nil;
    }
    return [target performSelector:method];
}

self是这两个参数中更有用的。实际上,这是使接收对象的实例变量可用于方法定义的方式。

获取方法地址

规避动态绑定的唯一方法是获取方法的地址并直接调用它,就好像它是一个函数一样。在少数情况下,当连续多次执行特定方法,并且您希望避免每次执行该方法时都要避免消息传递的开销时,这可能是合适的。

使用NSObject类中定义的方法,methodForSelector:,您可以要求一个指向实现方法的过程的指针,然后使用该指针来调用该过程。methodForSelector:返回的指针必须仔细转换为正确的函数类型。返回类型和参数类型都应包含在强制类型转换中。

下面的示例显示了如何setFilled:调用实现该方法的过程:

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

传递给过程的前两个参数是接收对象(self)和方法选择器(_cmd)。这些参数隐藏在方法语法中,但是在将方法作为函数调用时必须将其明确显示。

使用methodForSelector:规避动态绑定可以节省消息传递所需的大部分时间。但是,仅在重复多次重复一条特定消息的情况下,这种节省才是可观的,如for上面所示的循环。

请注意,这methodForSelector:是由Cocoa运行时系统提供的;它不是Objective-C语言本身的功能。

大千世界,求同存异;相遇是缘,相识是份,相知便是“猿粪”(缘分)
From MZou

上一篇下一篇

猜你喜欢

热点阅读