在iOS底层原理12:动态方法决议中探究了动态方法决议。在动态决议之后,通过日志辅助功能认识到forwardingTargetForSelector和methodSignatureForSelector方法,也就是消息发送的最后一个流程消息转发
准备工作
消息转发
消息发送在经过动态方法决议后,仍然没有查找到正真的方法实现,此时进入消息转发流程。转发流程分两步快速转发和慢速转发
快速转发流程
通过日志辅助发现,在崩溃之前会执行forwardingTargetForSelector方法,即消息快速流程
forwardingTargetForSelector方法探究
打开Xcode,通过快捷键command + shift + 0打开开发者文档,然后搜索forwardingTargetForSelector,结果如下图

- 根据开发者文档的描述,
forwardingTargetForSelector返回了一个重定向对象,这个对象来响应未实现的方法。
代码验证
- 新建一个
iOS工程,创建两个类HTPerson和HTCommon-
HTPerson类 只有实例方法sayHello、类方法sayBye的声明,无实现 -
HTCommon类 实现了这两个方法
-

- 在
HTPerson中添加forwardingTargetForSelector方法,代码如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [[HTCommon alloc] init];
} else if (aSelector == @selector(sayBye)) {
return [HTCommon class];
}
return [NSObject alloc];
}
- 运行程序,对象方法
sayHello已经成功调用了,但是类方法依然会导致崩溃

【问题】如何通过消息转发快速流程,来处理类方法呢?这里猜测需要通过+ (id)forwardingTargetForSelector:(SEL)aSelector {} 来处理类方法
- 继续修改
forwardingTargetForSelector方法,代码如下
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [[HTCommon alloc] init];
}
return [NSObject alloc];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayBye)) {
return [HTCommon class];
}
return [NSObject class];
}

慢速转发流程
如果通过快速转发流程forwardingTargetForSelector还是找不到方法实现,接下来苹果还给了我们一次机会,即慢速转发流程
- 慢速转发流程
methodSignatureForSelector,查看文档如下:

-
methodSignatureForSelector方法返回的是NSMethodSignature对象,该对象包含由给定选择器标识的方法的描述。methodSignatureForSelector一般和forwardInvocation搭配使用,如果methodSignatureForSelector方法返回的是一个nil就不会调用forwardInvocation
代码验证
#pragma mark- 处理对象方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayHello)) {
return [NSMethodSignature signatureWithObjCTypes:"v:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(sayHello)) {
HTCommon *common = [[HTCommon alloc] init];
anInvocation.target = common;
return [anInvocation invoke];
}
return [super forwardInvocation:anInvocation];
}
#pragma mark- 处理类方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(aSelector));
if (aSelector == @selector(sayBye)) {
return [NSMethodSignature signatureWithObjCTypes:"v:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector));
}
如果methodSignatureForSelector的返回值是NSMethodSignature对象,则会调用forwardInvocation方法对anInvocation事务进行处理,如果不处理也不会报错
消息转发总结
消息转发的处理主要分为两部分:
- 【
快速转发】当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法- 如果返回
消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程 - 如果返回
nil,则进入慢速消息转发
- 如果返回
- 【
慢速转发】执行到methodSignatureForSelector方法- 如果返回的方法签名为
nil,则直接崩溃报错 - 如果返回的方法签名
不为nil,走到forwardInvocation方法中,对anInvocation事务进行处理,如果不处理也不会报错
- 如果返回的方法签名为
方法调用流程

总结
至此,objc_msgSend发送消息的流程就分析完成了,我们可以得出整个方法调用的流程:
- 【
快速查找流程】:在类的缓存cache中查找指定方法的实现 - 【
慢速查找流程】:如果缓存中没有找到,则在类的方法列表中查找(二分查找),如果还是没找到,则去父类链的缓存和方法列表中查找 - 【
动态方法决议】:如果慢速查找还是没有找到时,第一次补救机会就是尝试一次动态方法决议,即重写resolveInstanceMethod/resolveClassMethod方法 - 【
消息转发】:如果动态方法决议还是没有找到,则进行消息转发,消息转发中有两次补救机会:快速转发+慢速转发 - 如果转发之后也没有,则程序直接报错崩溃
unrecognized selector sent to instance
缓存cache快速查找流程 --> 慢速查找流程 --> 动态决议方法resolveInstanceMethod --> 快速转发流程forwardingTargetForSelector --> 慢速转发流程(methodSignatureForSelector) --> resolveInstanceMethod --> forwardInvocation --> 崩溃报错
补充
hopper反汇编CoreFoundation系统库
查看崩溃时的堆栈信息,调用了CoreFoundation系统库的forwarding_prep_0 和 ___forwarding___方法,如下图

下载CoreFoundation源码,并没有找到这两个方法的实现,说明这块内容苹果并没有对外提供,只是开源了部分CoreFoundation源码
- 通过
image list获取所有的镜像文件列表,找到CoreFoundation库的文件路径

- 通过
objdump --macho --syms CoreFoundation | grep "forwarding"查看CoreFoundation库的符号表,发现___forwarding_prep_0___和____forwarding___都是本地符号

forwarding_prep_0方法
全局搜索__forwarding_prep_0___,发现只有一个,且会调用__forwarding__

____forwarding___方法
- 快速转发流程
- 如果
forwardingTargetForSelector方法没有实现,跳转loc_115baf流程 - 如果
forwardingTargetForSelector方法的返回值是nil,跳转loc_115baf流程
- 如果

-
慢速转发流程
- 如果
methodSignatureForSelector没有实现直接跳转到loc_115f4a流程,最终会进入loc_115fc5流程 - 如果
methodSignatureForSelector返回值等于nil跳转到loc_115fc5流程 - 如果
methodSignatureForSelector返回了签名信息的对象,则会调用_forwardStackInvocation:方法,最后会执行forwardInvocation方法

- 如果
慢速流程如果没有实现的话,则会进入
doesNotRecognizeSelector:方法

-
doesNotRecognizeSelector主要就是对崩溃信息的处理,以及输出报错信息










