本文将阐述OC中方法调用过程
- 消息发送
- 动态方法解析
- 消息转发
上一篇文章窥探OC中类的本质窥探了OC类的底层内部信息,也简单阐述了方法调用的逻辑,那么这篇文章将更深一步的探讨OC中方法调用的逻辑。
OC中方法的调用,其实都会转为objc_msgSend()函数的调用(除+load方法外)。objc_msgSend()的底层实现是汇编语言,这里就不展示,可以在源码库中的objc-msg-arm.s文件中查找到。objc_msgSen()函数其实包含了两个参数,一个是方法调用者(id),另一个是方法名(SEL),通过调用者可以找到其方法列表(类或者元类中的,以及父类中的),让再通过方法名去从中匹配出方法地址,进行调用。这些步骤是在能匹配到方法的情况下的,那当所有的方法列表匹配完了后又会进行怎样的操作呢,接下来将进行一系列的介绍。
Objc_msgSend()的执行分为三个阶段
- 消息发送阶段
- 动态方法解析阶段
- 消息转发阶段
那我们前面介绍的其实只是第一个阶段,现在我们再稍微梳理下第一阶段的流程。
消息发送阶段
1、消息接收者receiver是否为nil,如果receiver为nil直接退出(这点可以解释,我们在方法调用的时候,用一个空指针nil去调用方法的时候,不会crash的原因);
2、从receiver的Class的cache中查找方法,找到了就调用方法,查找结束;
3、从receiver的Class的class_rw_t中查找方法,找到了就调用方法并将其缓存在Class的cache_t中,结束查找;
4、判断上层是否有superClass,如果有则继续以下查找,往复,如果没有就进行到下一阶段(动态方法解析);
5、从receiver的superClass得cache中查找方法,找到了就调用方法查找结束;
6、从receiver的superClass的class_rw_t方法列表中查找方法,找到了就调用方法并将其缓存在Class的cache_t中,结束查找;
这里方法的缓存与查找逻辑,具体可以参考之前的文章窥探OC中类的本质有做详细介绍,这里不再展开说明。
以上步骤都执行完后,方法没有找到,那怎么办呢?就会进入下一个阶段,动态方法解析阶段。
动态方法解析
动态方法解析的步骤:
1、首先会判断是否曾经有动态解析,如果有,则进入到下一阶段(消息转发阶段)
2、如果没有,调用+resolveInstanceMethod-resolveInstanceMethod方法来动态解析方法
3、将动态解析的方法放到class_method_t中,并标记为已经动态解析过了,然后走流程1消息发送阶段
开发者可以实现以下方法,来动态添加方法实现:
1、+resolveInstanceMethod:
2、+resolveClassMethod:
动态解析过后,会重新走“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行
下面给出一个简单的动态方法解析示例:
void c_other(id self, SEL _cmd){NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));}+ (BOOL)resolveClassMethod:(SEL)sel{if (sel == @selector(test)) {// 第一个参数是object_getClass(self)class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");return YES;}return [super resolveClassMethod:sel];}+ (BOOL)resolveInstanceMethod:(SEL)sel{if (sel == @selector(test)) {// 动态添加test方法的实现class_addMethod(self, sel, (IMP)c_other, "v16@0:8");// 返回YES代表有动态添加方法return YES;}return [super resolveInstanceMethod:sel];}
如果经过动态方法解析阶段也没有匹配到方法调用的地址,那么将进入到第三个阶段,消息转发。
消息转发
消息转发实现逻辑:
1、调用forwardingTargetForSelector:方法,如果返回值不为nil,则向返回值发送消息objc_msgSend(返回值,方法)
2、如果返回值为nil,则调用methodSignatureForSelector:方法,返回值不为nil,则调用forwardInvocation:方法
3、如果返回值为nil,则调用doesNotRecognizeSelector:方法抛出异常crash
开发者可以在forwardInvocation:方法中自定义任何逻辑
以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
下面是小转发的示例
@implementation MJPerson+ (id)forwardingTargetForSelector:(SEL)aSelector{// objc_msgSend([[MJCat alloc] init], @selector(test))// [[[MJCat alloc] init] test]if (aSelector == @selector(test)) return [[MJCat alloc] init];return [super forwardingTargetForSelector:aSelector];}//+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector//{// if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];//// return [super methodSignatureForSelector:aSelector];//}////+ (void)forwardInvocation:(NSInvocation *)anInvocation//{// NSLog(@"1123");//}@end+ (void)test{NSLog(@"%s", __func__);}- (void)test{NSLog(@"%s", __func__);}
