介绍 消息发送以及相关的结构体
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 | struct objc_class { |
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 | struct objc_method_list { |
objc_cache
objc_cache:方法调用最先是在方法缓存里找的,方法调用是懒调用,第一次调用时加载后加到缓存池里。一个objc程序启动后,需要进行类的初始化、调用方法时的cache初始化,再发送消息的时候就直接走缓存(引申:+load方法和+initialize方法。load方法是首次加载类时调用,绝对只调用一次;initialize方法是首次给类发消息时调用,通常只调用一次,但如果它的子类初始化时未定义initialize方法,则会再调用一次它的initialize方法)
1 | struct objc_cache { |
Method
Method类型是一个objc_method结构体指针,而结构体objc_method有三个成员:
1 | typedef struct objc_method *Method; |
Method相关方法
1 | // 函数调用,但是不接收返回值类型为结构体 |
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 | /// Defines a property attribute |
其中,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;
int space OBJC2_UNAVAILABLE;
}
`
消息发送过程
当我们调用一个方法时,其运行过程大致如下:
- Runtime 系统会把方法调用转化为消息发送,即 objc_msgSend,并且把方法的调用者,和方法选择器,当做参数传递过去
- 方法的调用者会通过 isa 指针来找到其所属的类,然后在 cache 或者 methodLists 中查找该方法,找得到就跳到对应的方法去执行.(同时将匹配结果缓存在“快速映射表” (fast map)中。)
- methodLists 指向该类的实例方法列表,实例方法即-方法,那么类方法(+方法)存储在哪儿呢?类方法被存储在元类中,Class 通过 isa 指针即可找到其所属的元类.
- 如果在类中没有找到该方法,则通过 super_class 往上一级超类查找(如果一直找到 NSObject 都没有找到该方法的话,这种情况,开始动态方法解析
- NSObject 的超类为 nil,也就是它没有超类。
动态方法解析
如果某个对象调用了不存在的方法时会怎么样,一般情况下程序会crash
在程序crash之前,Runtime 会给我们动态方法解析的机会,消息发送的步骤大致如下:
- 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了.
- 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉.
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行.
如果 cache 找不到就找一下方法分发表. - 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止.
如果还找不到就要开始进入消息转发
消息转发机制
- 进入 resolveInstanceMethod: 方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过 class_addMethod 函数动态地添加方法,消息得到处理,此流程完毕.
- resolveInstanceMethod: 方法返回 NO 时,就会进入 forwardingTargetForSelector: 方法,这是 Runtime 给我们的第二次机会,用于指定哪个对象响应这个 selector。返回nil,进入下一步,返回某个对象,则会调用该对象的方法.
- 若 forwardingTargetForSelector: 返回的是nil,则我们首先要通过 methodSignatureForSelector: 来指定方法签名,返回nil,表示不处理,若返回方法签名,则会进入下一步.
- 当第 methodSignatureForSelector: 方法返回方法签名后,就会调用 forwardInvocation: 方法,我们可以通过 anInvocation 对象做很多处理,比如修改实现方法,修改响应对象等.
- 如果到最后,消息还是没有得到响应,程序就会crash