上一篇消息查找流程 探索了消息查找流程:
快速查找
和慢速查找
以及动态方法解析
。
但消息机制还不完整,如果动态方法解析
之后,仍无法找到IMP
,那又该如何处理呢?
消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
- 没有找到实现, 而且
动态方法解析
也没有帮助,就会进入消息转发流程
消息转发实现 __objc_msgForward_impcache
__objc_msgForward_impcache
是通过汇编实现,这里以arm64
为例。
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
-
__objc_msgForward_impcache
在arm64
下,只调用了__objc_msgForward
-
__objc_msgForward
调用了__objc_forward_handler
_objc_forward_handler
-
_objc_forward_handler
在C
源码中有默认实现
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
- 这里仅作为参考,实际似乎并没有走这里
- 这就说明
_objc_forward_handler
在某个地方被修改了 - 在源码中全局搜索
_objc_forward_handler
发现了objc_setForwardHandler()
函数
objc_setForwardHandler
- 打了个断点发现函数
objc_setForwardHandler
在dyld
初始化镜像::doImageInit
的时候触发的具体实现应该放在CoreFoundation
的__CFInitialize
中,但__CFInitialize
的并没有找到关于forwardHeadler
的实现(此路不通)。 - 换一种探索方式,既然分歧点从
_objc_msgForward_impcache
开始,那就给_objc_msgForward_impcache
下一个符号断点来窥探一二。
下符号断点来窥探闭源部分的内容
1. 给_objc_msgForward_impcache下符号断点
这里有一点要说明一下:测试Demo是在
x86_64
架构下运行的,和arm64
有一点差异
比如:在arm64
架构下只有_objc_msgForwrd
,没有_objc_msgForward_stret
进来了,激动
接着往下
2. _objc_msgForward
出现
_objc_forward_handler
和前面探索吻合,继续往下走
3 .forwarding_prep_0_
走到第22行,进入
___forwarding___
4. forwarding
第51行出现了
forwardingTargetForSelector:
,第60行发送该消息第93行出现了methodSignatureForSelector:
,第104行发送该消息
第183行出现了forwardInvocation:
,第199行发送该消息
第336行出现了doesNotRecognizeSelector:
,第347行发送该消息
- 从
___forwarding___
中可以得出消息转发的大致流程:
resolveInstanceMethod: > forwardingTargetForSelector: > methodSignatureForSelector: > forwardInvocation: - 汇编跟踪的效率太低,下面介绍一种更快捷的方法
从OC的消息发送日志入手
- 之前提到的
void instrumentObjcMessageSends(BOOL flag)
函数是控制是否记录OC
消息发送日志的函数 - 这个也可以用于
非源码环境
的工程中,在使用前需要引用一下该函数extern void instrumentObjcMessageSends(BOOL flag);
1. 写一个Demo
//引用instrumentObjcMessageSends函数
extern void instrumentObjcMessageSends(BOOL flag);
@interface Person : NSObject
- (void)sayInstance;
+ (void)sayClass;
@end
@implementation Person
/**
未实现
- (void)sayInstance;
+ (void)sayClass;
* 这里会报⚠️,只模拟消息转发的情况
*/
@end
int main(int argc, const char * argv[]) {
Person *p = [Person new];
//开启OC消息日志
instrumentObjcMessageSends(true);
//发送消息
[p sayInstance];
//关闭OC消息日志
instrumentObjcMessageSends(false);
return 0;
}
2. 找到msgSend-***,分析日志
- 在
/tmp
路径下回生成名为msgSend-***
//摘取了关键的日志信息
+ Person NSObject resolveInstanceMethod:
+ Person NSObject resolveInstanceMethod:
- Person NSObject forwardingTargetForSelector:
- Person NSObject forwardingTargetForSelector:
- Person NSObject methodSignatureForSelector:
- Person NSObject methodSignatureForSelector:
- Person NSObject class
+ Person NSObject resolveInstanceMethod:
+ Person NSObject resolveInstanceMethod:
- Person NSObject doesNotRecognizeSelector:
- Person NSObject doesNotRecognizeSelector:
- Person NSObject class
......
- 这里出现了几个方法:
-
resolveInstanceMethod:
不管是实例方法
还是类方法
的方法动态解析(未处理)
一定会调用NSObject resolveInstanceMethod:
,也就是说NSObject resolveInstanceMethod:
执行失败标志着方法动态解析失败
,也是消息转发
的必经之路;其中NSObject resolveInstanceMethod
之后如果还未得到解决
就结束了动态方法解析流程
,将进入了消息转发流程
-
forwardingTargetForSelector:
瞄一眼forwardingTargetForSelector:
苹果官方是如何描述的:
总结一下:
- 如果要让
forwardingTargetForSelector:
生效,就需要返回一个对象作为未识别消息
的接收者
,但是返回对象不能是self
否则将进入死循环
。- 如果实现了
forwardingTargetForSelector:
但是没有适合的对象作为新的接收者
,那就让super
处理。- 如果未实现
forwardingTargetForSelector:
就有机会调forwardInvocation:
methodSignatureForSelector:
总结一下:
methodSignatureForSelector:
实现并返回方法签名,是否进行转发的先决条件。- 返回了一个方法签名
NSMethodSignature *
,有了这个方法签名就会创建NSInvocation
。
doesNotRecognizeSelector:
- 官方的解释:处理一些接收者无法识别的消息
- 抛出异常
unrecognized selector sent to instance
-
forwardInvocation:
在forwardingTargetForSelector:
文档上提到了这么一个方法有点奇怪,实际上消息日志上并没有记录,不慌先看看forwardInvocation:
的官方说明
总结一下:
forwardInvocation:
的作用是将消息转发给其它对象forwardInvocation:
使用前必须实现methodSignatureForSelector:
forwardInvocation:
是从methodSignatureForSelector:
中获取需要转发的NSInvocation
对象
3. 验证猜想
现在Demo的现状运行就会报错
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person sayInstance]: unrecognized selector sent to instance 0x10052ddb0'
在Person
中实现消息转发的方法:
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
if (sel_isEqual(aSelector, @selector(sayInstance))) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
[super forwardInvocation:invocation];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
[super doesNotRecognizeSelector:aSelector];
}
@end
输出
***[7058:192271] -[Person forwardingTargetForSelector:] SEL sayInstance
***[7058:192271] -[Person methodSignatureForSelector:] SEL sayInstance
***[7058:192271] -[Person forwardInvocation:] SEL sayInstance
***[7058:192271] -[Person doesNotRecognizeSelector:] SEL sayInstance
***[7058:192271] -[Person sayInstance]: unrecognized selector sent to instance 0x1005af3d0
***[7058:192271] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person sayInstance]: unrecognized selector sent to instance 0x1005af3d0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff385f2d63 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff6e4e1bd4 objc_exception_throw + 48
2 CoreFoundation 0x00007fff3867d206 -[NSObject(NSObject) __retain_OA] + 0
3 forwrdingDemo 0x0000000100001cb3 -[Person doesNotRecognizeSelector:] + 131
4 forwrdingDemo 0x0000000100001c0e -[Person forwardInvocation:] + 174
5 CoreFoundation 0x00007fff385992c5 ___forwarding___ + 829
6 CoreFoundation 0x00007fff38598ef8 _CF_forwarding_prep_0 + 120
7 forwrdingDemo 0x0000000100001d37 main + 71
8 libdyld.dylib 0x00007fff6f840405 start + 1
9 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
- 调用顺序
1.forwardingTargetForSelector:
2.methodSignatureForSelector:
3.forwardInvocation:
4.doesNotRecognizeSelector
4. 使用forwardingTargetForSelector:将消息转发给指定对象
写一个新的类Friend
实现了 sayInstance
方法
@interface Friend : NSObject
@end
@implementation Friend
- (void)sayInstance {
NSLog(@"%s",__func__);
}
@end
在Person
的forwardingTargetForSelector:
方法中,将[Friend new]
作为sayInstance
的消息接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
if (sel_isEqual(aSelector, @selector(sayInstance))) {
return [Friend new];
}
return [super forwardingTargetForSelector:aSelector];
}
输出
***[7145:202044] -[Person forwardingTargetForSelector:] SEL sayInstance
***[7145:202044] -[Friend sayInstance]
Program ended with exit code: 0
-
sayInstance
转发成功,Friend
执行了sayInstance
-
forwardingTargetForSelector:
中指定了Friend
处理sayInstance
,结束了消息转发流程
5. 使用forwardInvocation:将消息转发给指定对象
先将Person
的forwardingTargetForSelector:
复原
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
在实现methodSignatureForSelector:
,对sayInstance
进行方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
if (sel_isEqual(aSelector, @selector(sayInstance))) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
最后实现forwardInvocation:
转发消息
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
if ([[Friend new] respondsToSelector:aSelector])
[invocation invokeWithTarget:[Friend new]];
else
[super forwardInvocation:invocation];
}
输出
***[7734:232196] -[Person forwardingTargetForSelector:] SEL sayInstance
***[7734:232196] -[Person methodSignatureForSelector:] SEL sayInstance
***[7734:232196] -[Person forwardInvocation:] SEL sayInstance
***[7734:232196] -[Friend sayInstance]
Program ended with exit code: 0
-
sayInstance
转发成功,Friend
执行了sayInstance
-
methodSignatureForSelector:
中提供了sayInstance
创建NSInvocation
对象的方法签名 -
forwardInvocation:
中做了简单的转发 -
NSInvocation
可以根据需求改变入参
或者返回参数
,这也是forwardInvocation:
转发的特点
附上消息转发的流程图
总结
- objc_msgSend 从缓存查找imp
- 慢速递归查找方法列表
- 方法动态解析
3.1resolveInstanceMethod:
实例方法解析
3.2resolveClassMethod:
类方法解析
3.3 由于isa
指向的独特性,实例方法
和类方法
最终都会掉[NSObject resolveInstanceMethod:]
3.4[NSObject resolveInstanceMethod:]
方法调用结束,标志着方法动态解析结束
- 消息转发
4.1forwardingTargetForSelector:
交给其它对象处理
4.2methodSignatureForSelector:
提供转发对象NSInvocation
需要的方法签名;
4.3forwardInvocation:
处理事务(将消息转发给其它对象)
4.4forwardingTargetForSelector:
和forwardInvocation:
的区别在于:前者参数和返回值需要和原方法一致,二后者没有这个限制 - doesNotRecognizeSelector: 处理接收者无法识别的消息(抛出异常)