Objective-C在编译时不是把[receiver message]当成简单的方法调用,而是把[receiver message]转化为:
如果消息含有参数,则为:
如果消息的接收者receiver能够找到对应的selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆玩完崩溃掉。
##什么是消息
进入正题之前,我们首先要来说说跟message息息相关的几个概念:
- message(消息)
message的具体定义很难说,因为并没有真正的代码描述,简单的讲message 是一种抽象,包括了函数名+参数列表,他并没有实际的实体存在。- method(方法)
method是真正的存在的代码。如:- (int)meaning { return 42; }- selector(方法选择器)
selector 通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作。
##两个跟消息相关的概念
###1. SEL
SEL又叫做方法选择器,是objc_msgSend函数第二个参数类型,那它的定义到底是什么呢?
打开objc.h文件,看下SEL的定义如下:
SEL是一个指向objc_selector结构体的指针。而 objc_selector 的定义并没有在runtime.h中给出定义。我们可以尝试运行如下代码:
输出如下:
Objective-C在编译时,会根据方法的名字生成一个用来区分这个方法的唯一的一个ID。
只要方法名称相同,那么它们的ID就是相同的。
两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么它的SEL就是一样的。每一个方法都对应着一个SEL。编译器会根据每个方法的方法名为那个方法生成唯一的SEL。
这些SEL组成了一个Set集合,这个Set简单的说就是一个经过了优化过的hash表。而Set的特点就是唯一,也就是SEL是唯一的,因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以了。
但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。那么,我们就不难理解,为什么SEL仅仅是函数名了。
###2. IMP
不同的类可以拥有相同的selector。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。
IMP在objc.h中是如此定义的:
IMP的本质就是一个函数指针,它是由编译器生成的。当发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。
前面介绍过的SEL,其实就是为IMP服务的。由于每个方法都对应唯一的SEL,因此我们可以通过SEL方便、快速、准确的获得它所对应的IMP(也就是函数指针),而在取得了函数指针之后,也就意味着我们取得了执行的时候的这段方法的代码的入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。
##传递消息所用的几个runtime方法
上篇文章中我们说过,下面的方法:
在编译后会变成:
实际上,同objc_msgSend方法类似的还有几个:
它们的作用都是类似的,为了简单起见,后续介绍消息和消息传递机制都以objc_msgSend方法为例。
下一节我们再继续讲述消息调用的流程。