本文将阐述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中查找方法”这一步开始执行

下面给出一个简单的动态方法解析示例:

  1. void c_other(id self, SEL _cmd)
  2. {
  3. NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
  4. }
  5. + (BOOL)resolveClassMethod:(SEL)sel
  6. {
  7. if (sel == @selector(test)) {
  8. // 第一个参数是object_getClass(self)
  9. class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
  10. return YES;
  11. }
  12. return [super resolveClassMethod:sel];
  13. }
  14. + (BOOL)resolveInstanceMethod:(SEL)sel
  15. {
  16. if (sel == @selector(test)) {
  17. // 动态添加test方法的实现
  18. class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
  19. // 返回YES代表有动态添加方法
  20. return YES;
  21. }
  22. return [super resolveInstanceMethod:sel];
  23. }

如果经过动态方法解析阶段也没有匹配到方法调用的地址,那么将进入到第三个阶段,消息转发。

消息转发

消息转发实现逻辑:

1、调用forwardingTargetForSelector:方法,如果返回值不为nil,则向返回值发送消息objc_msgSend(返回值,方法)

2、如果返回值为nil,则调用methodSignatureForSelector:方法,返回值不为nil,则调用forwardInvocation:方法

3、如果返回值为nil,则调用doesNotRecognizeSelector:方法抛出异常crash

开发者可以在forwardInvocation:方法中自定义任何逻辑

以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

下面是小转发的示例

  1. @implementation MJPerson
  2. + (id)forwardingTargetForSelector:(SEL)aSelector
  3. {
  4. // objc_msgSend([[MJCat alloc] init], @selector(test))
  5. // [[[MJCat alloc] init] test]
  6. if (aSelector == @selector(test)) return [[MJCat alloc] init];
  7. return [super forwardingTargetForSelector:aSelector];
  8. }
  9. //+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  10. //{
  11. // if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
  12. //
  13. // return [super methodSignatureForSelector:aSelector];
  14. //}
  15. //
  16. //+ (void)forwardInvocation:(NSInvocation *)anInvocation
  17. //{
  18. // NSLog(@"1123");
  19. //}
  20. @end
  21. + (void)test
  22. {
  23. NSLog(@"%s", __func__);
  24. }
  25. - (void)test
  26. {
  27. NSLog(@"%s", __func__);
  28. }