众所周知,在 Objective-C 中,如下的消息发送
[receiver message];
会被编译器转换为
objc_msgSend(receiver, @selector(message));
这样,实际的函数调用在运行时(runtime)才能确定,即所谓的动态绑定。
要了解 objc_msgSend
的具体实现,需要先了解 Objective-C 运行时中对象,类和方法的实现以及它们的关系。
对象
在 Objective-C 中,有一个类型 id
,用来表示指向对象的指针,它的定义能在 Objective-C 运行时的公开头文件 objc.h
中找到
/// A pointer to an instance of a class.
typedef struct objc_object *id;
显然 objc_object
结构体就是 Objective-C 运行时中用来表示对象的数据结构了,它的定义也能在 objc.h
中看到
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
Class
类型代表的肯定是类,isa
这个名字也很直观(is a some class)。因为结构体只是一个内存排布的约定,所以任何第一个成员是 Class
类型的结构体都可以视作为对象。
类和元类
在 objc.h
中,可以看到 Class
的定义
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
所以 Class
准确来说是指向类的指针,而 objc_class
结构体就是类真正的实现了。在公开头文件 runtime.h
中,可以看到 objc_class
结构体的定义
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
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
注意到 objc_class
结构体的第一个成员也是 Class isa
,这样它也能被当作是一个对象。接下来是 Class super_class
,用来指向父类。再者可以看到 objc_class
结构体有一个叫 methodLists
的成员,用来存放类的实例方法。
现在就有了两个问题,类的 isa
指向什么?类的类方法存放在哪里呢?这两个问题的答案都是「元类(meta-class)」。
每个类都有一个对应的元类,类的 isa
指向其对应的元类,对应的元类中存放着类的类方法。显然又有了新的问题,元类的类型也是 Class
,那它的 isa
和 super_class
指向哪里呢?元类的 super_class
很自然的指向其对应类的父类的元类,而 isa
则指向「根类(root class)」所对应的元类。
这篇文章对元类做了非常好的解释,并且有一张很好的对对象、类和元类关系的图示
可以看到,根类的 super_class
指向 nil
,根类对应的元类的 super_class
指向根类。
方法
在 objc_class
结构体中可以看到存放方法的成员类型是 objc_method_list
,它的定义能在 runtime.h
中找到
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
很显然,objc_method
结构体就是表示方法的数据结构,runtime.h
中对 Method
类型的定义也证明了这点
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
同样在 runtime.h
中,可以找到 objc_method
结构体的定义
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
其中 SEL
的定义可以在 objc.h
中找到
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
实际上,objc_selector
结构体根本就没有实现,这里就是它唯一存在的地方了。所以 SEL
可以看作是 void *
。
method_types
这个成员是方法的编码,具体的规则在官方文档里有一些说明。
IMP
在 objc.h
中的定义是这个样子的
typedef id (*IMP)(id, SEL, ...);
所以 IMP
其实是一个函数指针,第一个参数是一个对象,第二个参数是一个方法名。这两个参数其实就对应着 self
和 _cmd
。
这么看来,方法的表示还是很清晰的,就是方法名、方法的类型和具体实现。
消息发送
首先看一下公开头文件 message.h
中 objc_msgSend
的声明
id objc_msgSend(id self, SEL _cmd, ...);
然后可以继续 objc_msgSend
函数的大概逻辑了:
- 检查
self
是否为nil
,是的话直接返回nil
,这就是为什么可以给nil
发消息; - 通过
self
的isa
获取到类(或元类); - 使用
_cmd
在类(或元类)的方法中进行查找,没有找到的话,通过类(或元类)的super_class
继续查找; - 找到了对应的方法,调用其
IMP
; - 没有找到对应的方法,进入动态方法解析甚至之后的消息转发过程。
每次都要查找,对一个会被调用成千上万次的函数来说,开销实在是太大了。所以就要给它加上缓存,也就是 objc_class
结构体中的 struct objc_cache *cache
。可以在 runtime.h
中看到 objc_cache
结构体的信息
typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;
#define CACHE_BUCKET_NAME(B) ((B)->method_name)
#define CACHE_BUCKET_IMP(B) ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
所以 objc_msgSend
加上缓存的逻辑后:
- 检查
self
是否为nil
,是的话直接返回nil
; - 检查缓存中是否有对应的
_cmd
,有的话,调用其IMP
并返回; - 缓存未命中,通过
self
的isa
获取到类(或元类); - 使用
_cmd
在类(或元类)的方法中进行查找,没有找到的话,通过类(或元类)的super_class
继续查找; - 找到了对应的方法,将其存入缓存,调用其
IMP
; - 没有找到对应的方法,进入动态方法解析甚至之后的消息转发过程。
总结
知道了 Objective-C 中对象和类是如何表示和关联后,objc_msgSend
的原理和逻辑还是比较清晰的。之所以 Objective-C 中对象和类是如此表示,其实是一开始是受到 Smalltalk 的影响,很多东西都是直接照搬过来(甚至消息发送的语法,nil
等)。
上面的代码中都可以看到 OBJC2_UNAVAILABLE
这个宏,宏如其名,标上这个宏的东西其实是在 Objective-C 2.0 也就是我们现在所使用的 Objective-C 中,是过时的。现在的实现在细节方面已经有了较大的变化,但整体的思路并没有改变,所以这篇文章中的逻辑在现在的实现中并没有改变。