一.从objc_msgSend开始说
[receiver message]
//会被编译器转化为
objc_msgSend(receiver, selector)
所以当我们调用一个方法时,会执行的过程大致如下:
1.rutime系统会把方法调用转化为消息发送,并且把方法的调用者,和方法选择器,当做参数传递过去。
2.方法的调用者会通过isa 指针来找到所属的类,然后在 cache 或者 methodLists 中查找该方法,找得到就跳到对应的方法去执行。
3.如果在类中没找到该方法,则通过super_class 往上一级超类查找。如果一直找到 NSObject 都没有找到该方法的话,可能就会触发到消息转发。
二. runtime 的术语的数据结构
上面讲的是执行过程里面有一些术语接下来大概介绍下。
1.SEL
selector 是方法选择器,其实作用就和名字一样,日常生活中,我们通过人名辨别谁是谁。
Objective-C在编译的时候,会根据方法的名字,生成一个用 来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字相同,那么它们的ID都是相同的。就是说,不管是超类还是子类,不管是有没有超类和子类的关系,只要名字相同那么ID就是一样的。
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
这样的函数则被认为是一种编译错误,而这最终导致了一个非常非常奇怪的Objective-C特色的函数命名:
-(void)setWidthIntValue:(int)width;
-(void)setWidthDoubleValue:(double)width;
本质上,SEL只是一个指向方法的指针,它的存在只是为了加快方法的查询速度。
2.Class
Class 其实是指向 objc_class 结构体的指针
3.Method
Method 代表类中某个方法的类型
4.Ivar
Ivar 是表示成员变量的类型。
5.IMP
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。
如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面 Cache 中会提到。
你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id和 SEL 参数就能确定唯一的方法实现地址。
而一个确定的方法也只有唯一的一组 id 和 SEL 参数。
6.Cache
Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。