objc_msgSend()函数和_objc_msgForward函数作用

前言

[ receiver message ];

objective-c的这种语法被苹果称为“发消息”。与其他面向对象语言(C++/Java)的“方法调用”不同,objc的消息机制是由运行时实现、非常灵活动态。这篇文章简单记录一下objc运行时对于消息发送和转发的实现。

这里主要涉及到两个函数objc_msgSend()函数和_objc_msgForward()函数。

objc_msgSend()函数

作用

objc运行时用于消息发送

如何实现消息发送:

1. [receiver message]会由编译器转化为以下的纯C调用。

1
objc_msgSend(receiver, @selector(message));

官方文档中:

1
id objc_msgSend ( id self , SEL _cmd , . . . )

其中,self是消息的接受者,_cmd是selector, …是可变参数列表。根据消息的类型自动编译。

当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

2. objc_msgSend都做了什么,才动态绑定到应该执行的函数。

首先,先需要了解:

编译器构建每个类和对象时所采用的数据结构。每个类都包含以下两个必要元素:

一个指向父类的指针isa。通过该指针,对象可以找到它所属的类,也就找到了其全部父类。
一个调度表(dispatch table)。该调度表将类的selector与方法的实际内存地址关联起来。

objc_msgSend的真正行为:
  1. objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。
  2. 如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。
  3. 一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。
  4. 为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的selector以及继承自父类的selector。
  5. 查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。缓存命中的情况下,消息发送(messaging)比直接调用方法(function call)只慢一点点点点。

那么如果直到NSObject类都没有找到该selector呢?这就需要消息转发了!用_objc_msgForward函数指针代替要执行的函数指针。然后执行_objc_msgForward函数。

_objc_msgForward函数

作用:

objc运行时用于消息转发

如何实现

_objc_msgForward消息转发做了如下几件事:

  1. 调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。

  2. 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。

  3. 调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。

  4. 调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

上面这4个方法均是模板方法,开发者可以override,由runtime来调用。最常见的实现消息转发,就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的。

实践

1.如果想要自己观察一遍消息是如何转发的,可以开启调试模式、打印出所有运行时发送的消息。在代码中执行(void)instrumentObjcMessageSends(YES);方法,之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx文件里了。

2.看clang翻译的代码需要明白这些:

  • id 指代objc中的对象,每个对象的在内存的结构并不是确定的,但其首地址指向的肯定是isa。通过isa指针,运行时就能获取到objc_class。

  • objc_class 表示对象的Class,它的结构是确定的,由编译器生成。

  • SEL 表示选择器,这是一个不透明结构体。但是实际上,通常可以把它理解为一个字符串。例如printf(“%s”,@selector(isEqual:))会打印出”isEqual:”。运行时维护着一张SEL的表,将相同字符串的方法名映射到唯一一个SEL。 通过sel_registerName(char *name)方法,可以查找到这张表中方法名对应的SEL。苹果提供了一个语法糖@selector用来方便地调用该函数。

  • IMP 是一个函数指针。objc中的方法最终会被转换成纯C的函数,IMP就是为了表示这些函数的地址。

参考

Objective-C中的消息与消息转发

Objective-C消息机制的原理