1、简介

OC方法调用,其实都是发送消息,底层使用objc_msgSend进行发送,主要分为三个阶段:
第一阶段:消息发送
第二阶段:动态方法解析
第三阶段:消息转发

2、消息发送objc_msgSend

调用方法底层是通过objc_msgSend实现的。
OC代码:

  1. [person test];

C++代码:

  1. objc_msgSend(person, sel_registerName("test"))

为了方便阅读可以写成:

  1. objc_msgSend(person, @selector(test))

objc_msgSend方法中有两个参数,一个是消息接收者(receiver),一个是消息名称。
objc_msgSend源码是由汇编和C++代码实现的,源码分析略。
消息发送过程如下:
image.png

3、动态方法解析

当消息发送阶段结束,没有找到方法,就会进入动态方法解析阶段,系统给实例方法提供了resolveInstanceMethod方法,给类方法提供了resolveClassMethod方法,给没找到的方法添加实现的机会。添加方法完成后,会重新走“消息发送”的流程,从“在receiverClass的cache中查找方法”这一步开始执行。
比如person实例对象调用了test方法,但是test方法没有实现,可以按照下面方式给test方法添加实现。

  1. - (void)other {
  2. NSLog(@"%s",__func__);
  3. }
  4. + (BOOL)resolveInstanceMethod:(SEL)sel {
  5. NSLog(@"%s",__func__);
  6. // 动态添加test方法实现
  7. if (sel == @selector(test)) {
  8. // 获取其它方法
  9. Method method = class_getInstanceMethod(self, @selector(other));
  10. // 给sel(test)添加方法实现
  11. class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
  12. return YES;
  13. }
  14. return [super resolveInstanceMethod:sel];
  15. }

也可以添加c方法实现:

  1. void c_other(id self, SEL _cmd)
  2. {
  3. NSLog(@"c_other-%@-%@",self,NSStringFromSelector(_cmd));
  4. }
  5. + (BOOL)resolveInstanceMethod:(SEL)sel {
  6. NSLog(@"%s",__func__);
  7. // 动态添加test方法实现
  8. if (sel == @selector(test)) {
  9. class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
  10. return YES;
  11. }
  12. return [super resolveInstanceMethod:sel];
  13. }

类方法原理相同,注意添加方法的对象应传入类对象:

  1. + (BOOL)resolveClassMethod:(SEL)sel {
  2. // 动态添加test方法实现
  3. if (sel == @selector(test)) {
  4. class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
  5. return YES;
  6. }
  7. return [super resolveInstanceMethod:sel];
  8. }

动态解析过程如下:
image.png

4、消息转发

4.1、转发接收者

动态方法解析失败后会开始快速转发流程,指定一个能处理该方法的接收者,让接收者执行此方法。比如将Person的test方法,转发给Cat执行test方法:

  1. // 转发接收者,指定一个新的消息接收者
  2. - (id)forwardingTargetForSelector:(SEL)aSelector {
  3. if (aSelector == @selector(test)) {
  4. return [[Cat alloc] init];
  5. }
  6. return [super forwardingTargetForSelector:aSelector];
  7. }

这一步如果返回新的接受者,也会重新走“消息发送”的流程,可参考forwarding源码:forwardingclean.c

4.2、转发调用

转发接收者失败,会进入转发调用流程,先返回一个转发方法签名,根据这个方法签名系统会创建一个NSInvocation对象,用于处理转发工作。

  1. // 返回方法签名:返回值类型、参数类型
  2. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  3. if (aSelector == @selector(test)) {
  4. return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
  5. }
  6. return [super methodSignatureForSelector:aSelector];
  7. }
  8. // NSInvocation封装了一个方法调用,包括方法调用者、方法名、方法参数
  9. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  10. // 让Cat对象去执行上一步中的方法
  11. [anInvocation invokeWithTarget:[[Cat alloc] init]];
  12. }

消息转发过程如下:
image.png

4.3、补充

4.3.1、方法签名

v16@0:8这样的类型编码作为返回值,可以不写数字:

  1. // 返回方法签名
  2. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  3. if (aSelector == @selector(test)) {
  4. return [NSMethodSignature signatureWithObjCTypes:"v@:"];
  5. }
  6. return [super methodSignatureForSelector:aSelector];
  7. }

也可以通过创建接收者实例对象找到对应的方法签名:

  1. // 返回方法签名
  2. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  3. if (aSelector == @selector(test)) {
  4. return [[[Cat alloc] init] methodSignatureForSelector:@selector(test)];
  5. }
  6. return [super methodSignatureForSelector:aSelector];
  7. }

4.3.2、类方法消息转发

需要将消息转发方法换位类方法,并注意返回对象为类对象,转发接收者:

  1. // 转发接收者
  2. + (id)forwardingTargetForSelector:(SEL)aSelector {
  3. if (aSelector == @selector(test)) {
  4. return [Cat class];
  5. }
  6. return [super forwardingTargetForSelector:aSelector];
  7. }

转发调用:

  1. + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  2. if (aSelector == @selector(test)) {
  3. return [NSMethodSignature signatureWithObjCTypes:"v@:"];
  4. }
  5. return [super methodSignatureForSelector:aSelector];
  6. }
  7. + (void)forwardInvocation:(NSInvocation *)anInvocation {
  8. NSLog(@"......");
  9. }

4.3.3、invocation

当消息转发流程进入forwardInvocation:方法后,可以利用invocation获取方法参数、获取方法返回值等数据。

4.4.4、@dynamic

@dynamic就是通知编译器,不要给age自动生成setter和getter方法的实现,不自动生成员变量。(不影响setter/getter声明)

  1. @dynamic age;

添加后调用person.age会报错,提示找不到方法。