iOS:Runtime - objc_msgSend函数

笔记记录:来源于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, ...)

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

  • 它首先找到选择器的过程(方法实现)指。由于相同的方法可以通过不同的类不同地实现,因此它找到的精确过程取决于接收者的类。
  • 然后,它将调用该过程,并将接收对象(指向其数据的指针)以及为该方法指定的所有参数传递给该过程。
  • 最后,它将过程的返回值作为自己的返回值传递。

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

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

  • 指向超类的指针。

  • 调度表。该表具有将方法选择器与其所标识的方法的类特定地址相关联的条目。setOrigin::方法的选择器与(实现的过程)的地址相关联,方法setOrigin::的选择器displaydisplay的地址相关联,依此类推。
    创建新对象时,将为其分配内存,并初始化其实例变量。对象变量中的第一个是指向其类结构的指针。该指针称为isa,它使对象可以访问其类,并可以通过该类访问其继承的所有类。
    **注意:** 虽然严格来说,语言不是语言的一部分,但isa对象与Objective-C运行时系统一起使用时需要使用指针。一个对象必须与一个对象“等效”。struct objc_object(在objc/objc.h中定义)在结构定义的任何字段中。但是,很少(如果有的话)需要创建自己的根对象,并且从该变量继承NSObjectNSProxy自动具有该isa变量的对象。

这些类和对象结构的元素如图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

你可能感兴趣的:(iOS:Runtime - objc_msgSend函数)