我们在 OC底层原理13-动态方法决议 一文中,分析了动态方法决议,调试的时候发现resolveInstanceMethod
和resolveClassMethod
方法如果在类中重写,但是没有添加方法的时候,这2个方法分别会被调用2次,然后才会崩溃,为什么被调了2次呢?抱着这个疑问开始了源码断点调试方法执行顺序的研究
一、方法慢速查询 -> 动态方法决议 -> 消息转发流程图
二、快速消息转发forwardingTargetForSelector
2.1 forwardingTargetForSelector
官方文档
2.2 消息快速转发
GomuPerson.m
//- (void)sayNO{ NSLog(@"调用:%s",__func__); }
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 这里如果不处理依然会崩溃,并且转发的接受者必须实现了sayNO
return [GomuBoy alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
GomuBoy.m
//: -- 实现方法
- (void)sayNO{ NSLog(@"调用:%s",__func__); }
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
调用:-[GomuBoy sayNO]
我们成功的把
GomuPerson
中没有实现的方法sayNO
,转发给毫无关系但是实现了sayNO
方法的GomuBoy
,有效的防止了方法未实现发送的崩溃当您只想将
消息重定向
到另一个对象
,并且比常规转发快一个数量级
时,此功能很有用。快速速消息转发
只能重定向消息的接受者
forwardingTargetForSelector
可以写在本类
或者父类
或者它们的分类
中,都能起到消息快速转发
的作用
三、慢速消息转发methodSignatureForSelector
3.1 methodSignatureForSelector
文档
3.2 消息慢速转发
//: -- 单独使用此方法也会崩溃,必须配合forwardInvocation使用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
//: -- 注意:这里返回nil,会报错`unrecognized selector sent to instance 0x100752600`
//: -- 返回签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//: -- 事务处理,可以重定向当前方法的接收者(GomuBoy),也可以重定向方法(sayCode)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(sayNO)) {
//: -- 注意: 下面可以不实现,也不会崩溃,但是forwardInvocation方法必须实现
//: -- 下面2种处理方式二选一
//: -- 重定向方法 sayNO-> sayCode
// anInvocation.selector = @selector(sayCode);
//: -- 重定向消息接受者 GomuPerson -> GomuBoy
anInvocation.target = [GomuBoy alloc];
//: -- 调用
[anInvocation invoke];
}
}
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印
-[GomuPerson methodSignatureForSelector:] ----- sayNO
-[GomuPerson forwardInvocation:] ----- sayNO
调用:-[GomuBoy sayNO]
methodSignatureForSelector
必须配合forwardInvocation
使用,才能防止崩溃
unrecognized selector sent to instance
错误是由methodSignatureForSelector
方法返回nil
的时候抛出的forwardInvocation
方法里面不处理事务也不会崩溃,只需要配合methodSignatureForSelector
实现就能防止崩溃
慢速消息转发
不仅可以重新向消息的接受者
,也可以重定向消息
慢速消息转发
的使用场景:可以对当前没有实现的方法
,进行保存,想什么时候转发都可以,即维护一个不立即处理的消息
methodSignatureForSelector
和forwardInvocation
也可以写在本类
或者父类
或者它们的分类
中,都能起到消息慢速转发
的作用
四、resolveInstanceMethod、forwardingTargetForSelector、methodSignatureForSelector 比较
4.1 作用
resolveInstanceMethod
重定向消息对象sayNO -> sayCode
forwardingTargetForSelector
重定向消息接受者GomuPerson -> GomuBoy
methodSignatureForSelector
即可以重定向消息对象,也可以重定向消息接受者
4.2 使用场景
resolveInstanceMethod
:立即调用,并且只需要重定向消息对象
forwardingTargetForSelector
:立即调用,并且只需要重定向消息接收者
methodSignatureForSelector
:可以任何时候调用,可以重定向消息对象
,也可以重定向消息接收者
4.3 互斥性:只要有一个方法对消息进行了处理,就不会走后面流程
如果
resolveInstanceMethod
对当前sel
进行了处理,重定向了消息对象,就不会再调用forwardingTargetForSelector
,methodSignatureForSelector
如果
resolveInstanceMethod
没有处理,forwardingTargetForSelector
重定向了消息接受者,methodSignatureForSelector
就不会调用
五、通过方法调用验证上面流程图
//: -- 动态决议,只调方法不实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(sel));
}
return [super resolveInstanceMethod:sel];
}
//: -- 消息开始转发,只调方法不实现
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
}
return [super forwardingTargetForSelector:aSelector];
}
//: -- 消息慢速转发,只调方法不实现
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
}
return [super methodSignatureForSelector:aSelector];
}
//: -- 事务处理,只调方法不实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if (anInvocation.selector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(anInvocation.selector));
}
}
//: -- 三次机会都没处理,调用这里
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
if (aSelector == @selector(sayNO)) {
NSLog(@"%s ----- %@",__func__,NSStringFromSelector(aSelector));
}
[super doesNotRecognizeSelector:aSelector];
}
打印:
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson forwardingTargetForSelector:] ----- sayNO
-[GomuPerson methodSignatureForSelector:] ----- sayNO
+[GomuPerson resolveInstanceMethod:] ----- sayNO
-[GomuPerson doesNotRecognizeSelector:] ----- sayNO
- 再次验证了我们上面源码断点分析出的流程正确
六、拓展知识
6.1 监听objc底层消息发送,打印相关调用方法日志
//: -- 扩展:当前方法不在该类中,但是在其他地方有申明
//: -- 扩展声明系统没有提供的方法,才能直接调用
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
GomuPerson *p = [GomuPerson alloc];
//: -- 用该方法包裹研究对象,打印出来的就是对应的日志
instrumentObjcMessageSends(YES);
[p sayNO];
instrumentObjcMessageSends(NO);
}
return 0;
}
- 日志路径
/tmp/msgSends/
,如下图msgSends-11981
日志路径.png -
打开日志,会发现我们前面流程图的调用顺序,整个消息转发机制调用方法(msgSend)的流程,如图
消息转发机制调用方法(msgSend)的流程.png