消息传递
OC
语言中,对象调用方法我们称之为发送消息,或者叫做“传递消息”(pass a message
)。消息具有“名称”(name
)或“选择子”(selector
),实际上就是方法名称,可以接受参数,也可以有返回值。在 OC
中我们是这样发送消息的,结构如下:
id retrunValue = [someObject messageName:parameter];
其中returnValue
是返回值;someObject
是接受者,即调用这个方法的对象;messageName:
是选择子,即方法名称;parameter
是参数,即调用方法的时候传入的参数。选择子(selector
)与参数(parameter
)合起来就是我们称为的“消息”。在程序编译期的时候,所有类似这样的消息最终都会被转换成一条标准的C
语言函数内供程序调用,其结构如下:
id objc_msgSend ( id self, SEL cmd, ... );
这是一个参数个数可变的函数,能够接收多个的参数,其中第一个参数代表接收者,第二个参数代码选择子(SEL
是选择子的类型),后面其他的消息就是发送消息中所传入的参数。
讲上面的那个OC
的例子转换后的原型如下:
id returnValue = objc_msgSend(someObject, @selector(messageName:),parameter);
在程序运行的时候objc_msgSend
函数会根据接受者和选择子在相对应的类中查找方法,在对应类中有一个“方法列表”如果能找到对应的方法就执行此方法,若不能就沿着继承体向上父类继续查找,如果最终还是找不到相对应的方法就会执行“消息转发(message forwarding
)”。
在调用方法的时候执行的步骤很多,但是objc_msgSend
会将匹配到的方法列表中的方法缓存起来,其缓存在一个叫“快速映射表”里面。每一个类都有这样的一块缓存,在该类下次再执行相同的消息的时候就会优先从“快速映射表”中查找,而选择子就是查找方法时所用到的键。
objc_object
, objc_class
以及 Ojbc_method
在OC
中,类,对象和方法其实都是一个C
的结构体,这点我忙在objc/objc.h
和runtime.h
的文件中就能找到他们的定义。
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
typedef struct objc_method *Method;
objc_object
实际上是一个指向 Class
结构体类型的指针,指向对象的类,而 Class
中也有一个 isa
指针,指向了元类,元类中则存储了该类的方法列表objc_method_list
。
objc_method_list
本质是一个有 objc_method
元素的可变长度的数组。一个 objc_method
结构体中有函数名,也就是SEL
,有表示函数类型的字符串,以及函数的实现IMP
。
objc_class
就是前面说到的被objc_object
中那个 isa
指针所指向的 Class
的类了。这个objc_class
其实是一个结构体,其中在这个结构体中就包含了很多这个类的信息。其中就包括了上文中提到的储存这个类中当前方法的链表objc_method_list
和储存这个类中被执行过的方法的缓存objc_cache
等相关的信息。
总结
那么总结来说当在代码中写下一个 OC
语言发送的消息后,那么这条 OC
语言的消息首先经过编译器,就会被编译器转换成这样类似结构的一条 C
语言函数:
id objc_msgSend ( id self, SEL cmd, ... );
举个例子🌰:
objc_msgSend(dog,@selector(eat:),)
1.首先这个函数就会根据传入的dog
这个类的 isa
指针找到他的 class
即objc_class
2.在 class
中找到这个类的所有的相关信息
3.然后会首先的从 class
中的objc_cache
去查找eat
方法
4.如果在objc_cache
中没有找到 eat
这个方法,则说明 eat
方法没有被执行过,在缓存中没有
5.然后进一步到这个类中的objc_method_list
中找和选择器中对应的方法eat
6.如果在 class
中没有找到相关的方法,则会继续向上查找,在父类(super_class
)中去找相对应的方法
7.一旦找到这个方法,就去执行它的实现 IMP
。
但是如果最终一直都没有找到 eat
这个相对应的方法呢?通常程序就会crash
并抛出异常,但是在抛出异常前首先会执行消息转发,关于消息转发的相关学习和笔记我将会在下一篇的博客中整理出来。