介绍消息传递和转发
第一部分:从方法调用得出的objc_msgSend消息传递的结论
1.1 文字的描述:先来看一段代码:
ClangClass类有个handle方法
handle方法里面是创建AClass对象并调用addO方法
我们来看看调用addO方法的具体过程
1.2 源代码的描述:
#import "ClangClass.h"
#import "AClass.h"
@interface ClangClass()
@property(nonatomic,strong)AClass *acClass;
@end
@implementation ClangClass
- (instancetype)init{
if (self = [super init]) {
_acClass = [[AClass alloc] init];
}
return self;
}
- (void)handle{
[_acClass addO];
}
@end1.3 将代码操作一下:
进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m
看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];
{是不是感觉一大堆很乱,我也觉得}
得出结论:
调用方法addO就是调用objc_msgSend进行消息传递;
而objc_msgSend就是去找到IMP,来执行实现的代码;
第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret
objc_msgSend定义:id objc_msgSend(id self, SEL op, …);
id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
SEL的本质:SEL 是系统在编译过程中,根据方法的名字以及参数序列生成一个用来区分这个方法的唯一ID编号;这个ID就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
2.1 文字描述objc_msgSend的工作流程:
在self中沿着isa找到AClass的类对象;
优先在AClass的cache查找addO方法,如果有就返回imp;
如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;
如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;
判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;
如果没有在AClass找到addO方法,就会到super_class就是NSObject里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;
如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。
如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;
不管上面的哪一步找到imp,都直接返回imp。
2.2 相关:
_objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。
当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。
2.3 本质:
objc_msgSend的本质就是找到imp指针执行代码,而静态语言的imp函数指针在编译的时候就已经确定好了,动态语言是编译后可以变换的。
2.4 源代码描述objc_msgSend的工作流程:
第三部分:消息转发_objc_msgForward
3.1 oc转发文字描述:
调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。
如果调用resolveInstanceMethod没有实现,就开始调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。
如果调用forwardingTargetForSelector没有效果,就开始调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。
将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。
调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。
3.2 oc转发源代码描述:
第四部分 respondsToSelector、instancesRespondToSelector
4.1 文字分析:
如果没有找到imp方法,就尝试一次resolveInstanceMethod,不会进行消息转发
不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了
主要的原因,看respondsToSelector的源码就知道,在下面
4.2 源码分析:
第五部分 methodForSelector、instanceMethodForSelector
5.1 看源码可以知道,是和respondsToSelector一样的实现机制:没有消息转发
write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51
Last updated
Was this helpful?