objc_msgSend相关结构体 消息发送原理

介绍 消息发送以及相关的结构体
apple open source

objc_msgSend

给对象发送消息写法:

`id returnValue = [somneObject messageName:parameter];`

编译器会将上诉代码转换为:

`id = returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);`

其中 someObject叫做“接受者”(receiver),messageName叫做“选择子”(selector),选择子和参数合称“消息”

objc_msgSend 的定义: 该函数有两个参数,一个 id 类型,一个 SEL 类型。

`id objc_msgSend(id self, SEL op, ...)`;

SEL

其实它就是个映射到方法的C字符串,你可以用 Objective-C 编译器命令 @selector() 或者 Runtime 系统的 sel_registerName 函数来获得一个 SEL 类型的方法选择器。定义在在 objc/objc.h 目录下:

`typedef struct objc_selector *SEL;`    

id

id 是一个结构体指针类型,它可以指向 Objective-C 中的任何对象,id 也被定义在 objc/objc.h 目录下:

`typedef struct objc_object *id;`

Class

Class 也是一个结构体指针类型:

`typedef struct objc_class *Class;`

objc_class 结构体是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;//每个Class都有一个isa指针

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;//父类
const char *name OBJC2_UNAVAILABLE;//类名
long version OBJC2_UNAVAILABLE;//类版本
long info OBJC2_UNAVAILABLE;//!*!供运行期使用的一些位标识。如:CLS_CLASS (0x1L)表示该类为普通class; CLS_META(0x2L)表示该类为metaclass等(runtime.h中有详细列出)
long instance_size OBJC2_UNAVAILABLE;//实例大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//存储每个实例变量的内存地址
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;//!*!根据info的信息确定是类还是实例,运行什么函数方法等
struct objc_cache *cache OBJC2_UNAVAILABLE;//缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//协议
#endif

} OBJC2_UNAVAILABLE;

MetaClass

isa和super_class是找到实现函数的关键映射,决定找到存放在哪个类的方法实现。(isa用于自省确定所属类,super_class确定继承关系)。

实例对象的isa指针指向类,类的isa指针指向其元类(metaClass)。对象就是一个含isa指针的结构体。类存储实例对象的方法列表,元类存储类的方法列表,元类也是类对象

我们发现 Class 本身也有一个 isa 指针,指向的是它的 MetaClass。

  • 当我们对一个实例发送消息时(-开头的方法),会在该 instance 对应的类的 methodLists 里查找。
  • 当我们对一个类发送消息时(+开头的方法),会在该类的 MetaClass 的 methodLists 里查找。
  • 每个 Class 都有一个 isa 指针指向一个唯一的 Meta Class
  • 每一个 Meta Class 的 isa 指针都指向最上层的 Meta Class,即 NSObject 的 MetaClass,而最上层的 MetaClass 的 isa 指针又指向自己

objc_method_list

其中objc_method_list:用来存储当前类的方法链表,objc_method存储了类的某个方法的信息。

1
2
3
4
5
6
7
8
9
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;
}

objc_cache

objc_cache:方法调用最先是在方法缓存里找的,方法调用是懒调用,第一次调用时加载后加到缓存池里。一个objc程序启动后,需要进行类的初始化、调用方法时的cache初始化,再发送消息的时候就直接走缓存(引申:+load方法和+initialize方法。load方法是首次加载类时调用,绝对只调用一次;initialize方法是首次给类发消息时调用,通常只调用一次,但如果它的子类初始化时未定义initialize方法,则会再调用一次它的initialize方法)

1
2
3
4
5
6
7
8
9
struct objc_cache {
// 缓存bucket的总数
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;

// 实际缓存bucket的总数
unsigned int occupied OBJC2_UNAVAILABLE;
// 指向Method数据结构指针的数组
Method buckets[1] OBJC2_UNAVAILABLE;
};

Method

Method类型是一个objc_method结构体指针,而结构体objc_method有三个成员:

1
2
3
4
5
6
typedef struct objc_method *Method;
struct objc_method {
SEL method_name; // 方法名称
char *method_typesE; // 参数和返回类型的描述字串
IMP method_imp; // 方法的具体的实现的指针
}

Method相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 函数调用,但是不接收返回值类型为结构体
method_invoke
// 函数调用,但是接收返回值类型为结构体
method_invoke_stret
// 获取函数名
method_getName
// 获取函数实现IMP
method_getImplementation
// 获取函数type encoding
method_getTypeEncoding
// 复制返回值类型
method_copyReturnType
// 复制参数类型
method_copyArgumentType
// 获取返回值类型
method_getReturnType
// 获取参数个数
method_getNumberOfArguments
// 获取函数参数类型
method_getArgumentType
// 获取函数描述
method_getDescription
// 设置函数实现IMP
method_setImplementation
// 交换函数的实现IMP
method_exchangeImplementations

objc_property_t

objc_property_t代表属性,而它又是一个结构体指针:

`// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;`

objc_property是内置的类型,与之关联的还有一个objc_property_attribute_t,它是属性的attribute,也就是其实是对属性的详细描述,包括属性名称、属性编码类型、原子类型/非原子类型等。它的定义如下:

1
2
3
4
5
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

其中,value通常是空的,但是对于类型是有值的。

Ivar

成员变量通过Ivar表示,它是objc_ivar结构体指针:

`// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;`

而objc_ivar结构的定义为:

1
2
3
4
5
6
7
8
9
10
11
12
struct objc_ivar {
// 成员变量名
char *ivar_name OBJC2_UNAVAILABLE;
// 成员变量encode类型
char *ivar_type OBJC2_UNAVAILABLE;
// 基地址偏移字节
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
`

消息发送过程

当我们调用一个方法时,其运行过程大致如下:

  1. Runtime 系统会把方法调用转化为消息发送,即 objc_msgSend,并且把方法的调用者,和方法选择器,当做参数传递过去
  2. 方法的调用者会通过 isa 指针来找到其所属的类,然后在 cache 或者 methodLists 中查找该方法,找得到就跳到对应的方法去执行.(同时将匹配结果缓存在“快速映射表” (fast map)中。)
    • methodLists 指向该类的实例方法列表,实例方法即-方法,那么类方法(+方法)存储在哪儿呢?类方法被存储在元类中,Class 通过 isa 指针即可找到其所属的元类.
  3. 如果在类中没有找到该方法,则通过 super_class 往上一级超类查找(如果一直找到 NSObject 都没有找到该方法的话,这种情况,开始动态方法解析
      • NSObject 的超类为 nil,也就是它没有超类。

动态方法解析

如果某个对象调用了不存在的方法时会怎么样,一般情况下程序会crash

在程序crash之前,Runtime 会给我们动态方法解析的机会,消息发送的步骤大致如下:

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了.
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉.
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行.
    如果 cache 找不到就找一下方法分发表.
  4. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止.

如果还找不到就要开始进入消息转发

消息转发机制

  1. 进入 resolveInstanceMethod: 方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过 class_addMethod 函数动态地添加方法,消息得到处理,此流程完毕.
  2. resolveInstanceMethod: 方法返回 NO 时,就会进入 forwardingTargetForSelector: 方法,这是 Runtime 给我们的第二次机会,用于指定哪个对象响应这个 selector。返回nil,进入下一步,返回某个对象,则会调用该对象的方法.
  3. 若 forwardingTargetForSelector: 返回的是nil,则我们首先要通过 methodSignatureForSelector: 来指定方法签名,返回nil,表示不处理,若返回方法签名,则会进入下一步.
  4. 当第 methodSignatureForSelector: 方法返回方法签名后,就会调用 forwardInvocation: 方法,我们可以通过 anInvocation 对象做很多处理,比如修改实现方法,修改响应对象等.
    • 如果到最后,消息还是没有得到响应,程序就会crash