52个有效方法(11) - 理解objc_msgSend的作用
消息机制
OC 语言中,方法调用实际上就是消息发送。
objc_msgSend(id obj, SEL cmd,...)
这是一个参数个数可变的函数,能够接收多个的参数,其中第一个参数代表接收者,第二个参数代码选择子(SEL是选择子的类型),后面其他的消息就是发送消息中所传入的参数。
消息具有“名称”(name)或“选择子”(selector),实际上就是方法名称,可以接受参数,也可以有返回值。
发送消息的方式
在 OC 中我们是这样发送消息的:
id returnValue = [someObject messageName:parameter];
someObject叫做“接受者”(receiver),messageName叫做“选择子”(selector)。选择子与参数合起来称为“消息”(message).编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,就是objc_msgSend,编译器把上面的方法调用会转换为如下函数。
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
方法调用的过程
-
objc_msgSend函数会依据接受者与选择子的类型来调用适当的方法。
-
为了完成此操作,该方法需要在接受者所属的类中搜寻其“方法列表”(list of methods)。
-
如果能找到与选择子名称相符的方法,就跳至其实现代码;若是找不到,就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。
-
如果最终还是找不到相符的方法,那就执行“消息转发”(mesageforwarding)操作。
快速映射表
在调用方法的时候执行的步骤很多,但是objc_msgSend会将匹配到的方法列表中的方法缓存起来,其缓存在一个叫“快速映射表”里面。每一个类都有这样的一块缓存,在该类下次再执行相同的消息的时候就会优先从“快速映射表”中查找,而选择子就是查找方法时所用到的键。
方法调用的优先级
分类(最后参与编译的分类优先) --> 原来类 --> 父类
在OC中,类,对象和方法其实都是一个C的结构体
#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 _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
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
等相关的信息。
方法调用的具体实例
objc_msgSend(dog,@selector(eat:),meat);
-
首先这个函数就会根据传入的dog这个类的 isa 指针找到他的 class即objc_class。
-
在 class 中找到这个类的所有的相关信息。
-
然后会首先的从 class 中的objc_cache去查找eat 方法。
-
如果在objc_cache中没有找到 eat 这个方法,则说明 eat 方法没有被执行过,在缓存中没有。
-
然后进一步到这个类中的objc_method_list中找和选择器中对应的方法eat。
-
如果在 class 中没有找到相关的方法,则会继续向上查找,在父类(super_class)中去找相对应的方法。
-
一旦找到这个方法,就去执行它的实现 IMP(一个函数指针,保存了方法的地址)。
-
但是如果最终一直都没有找到 eat 这个相对应的方法呢?通常程序就会crash并抛出异常,但是在抛出异常前首先会执行消息转发。
要点
-
消息由接受者、选择子及参数构成。给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method)。
-
发给对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码。