在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
方法
image
- 如果
-
慢速流程如果没有实现的话,则会进入
doesNotRecognizeSelector:
方法

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

网友评论