unrecognized selector的底层实现原理
创建类Person和Teacher;
类Person:
@interface Person : NSObject
- (void)run;
- (void)talk;
@end
@implementation Person
- (void)run {
NSLog(@"-->%s", __func__);
}
@end
类Teacher:
@interface Teacher : Person
@end
@implementation Teacher
@end
接下来,在main函数中调用如下代码:
开发过程中,我们经常会碰到调用了一个没有实现的方法,然后会报错:unrecognized selector sent to instance 0x10*******,那么这个错误究竟是如何产生的,他的底层原理又是什么呢?
在上一篇文章中,我们在最后得出的结论, 如果方法最终没有找到对应的imp,就会赋值一个forward_imp
那么,forward_imp是什么东西呢?
我们来看一下_objc_msgForward_impcache里边究竟有什么?
我们在汇编代码中找到_objc_msgForward_impcache的汇编实现,它调用了__objc_msgForward:
在之前的分析中,我们知道TailCallFunctionPointer里边只有一个br $0,而$0是x17,我们根据代码可以分析出__objc_forward_handler操作之后,将结果给了x17,__objc_forward_handler才是我们需要重点研究的地方:
在__OBJC2__中,__objc_forward_handler对应赋值的是objc_defaultForwardHandler,而在objc_defaultForwardHandler的实现中,最终打印了**** unrecognized selector sent to instance ***
虽然我们找到了最终输出错误的地方,但是依然没有解决如何处理这个错误的问题,要想处理这个错误,那么就需要了解消息的处理流程
消息处理流程图
对象方法动态决议
接下来,我们结合源码分析talk方法的执行:
确定执行Teacher的talk方法时,进入lookUpImpOrForward方法内部,因为talk方法没有实现,所以代码将会执行到:
behavior是方法传进来的参数,从汇编源码中我们可以看到,它的值为3:
与打印结果一致:
behavior & LOOKUP_RESOLVER即3 & 2结果为2,所以进入判断
之后3 ^ 2结果为1,下次再走此判断是1 ^ 2结果为0,不会第二次进入该判断内部,此处相当于是个单例(自定义单例千万别这么写);
接下来进入方法resolveMethod_locked:
前边我们可以确定talk方法是没有实现的,那么resolveMethod_locked最终为什么还要去调用lookUpImpOrForwardTryCache去查找呢,那么极有可能是在上边的方法中对talk方法做了处理;
此处Teacher显然不是元类,将会执行resolveInstanceMethod方法:
根据代码分析,只要我们在类cls中处理了resolve_sel消息,那么就可以通过lookUpImpOrNilTryCache来得到一个imp;也就是需要在Teacher类中,实现resolveInstanceMethod方法;
由于是往cls,也就是类发送消息,那么应该是一个类方法resolveInstanceMethod:
在源码中,resolveInstanceMethod是有默认实现的
我们只要重写此方法
运行,查看打印结果:
虽然依然崩溃,但是很显然,在崩溃之前,我们在resolveInstanceMethod中拦截到了talk方法的执行,那么就可以动态的给talk添加一个实现imp:
最终动态的给talk添加了imp也就是tempTalk;
类方法动态决议
接下来给Person添加类方法+(void)smile,同样不实现;用Teacher类来调用分析结果:
因为方法没有实现,所以会崩溃!断点之后,发现调用了resolveClassMethod方法
进入resolveClassMethod方法,发现其与resolveInstanceMethod实现十分相似;不同地方在于resolveInstanceMethod里边需要类中实现一个类方法resolveInstanceMethod,而resolveClassMethod是需要在元类里实现一个对象方法resolveClassMethod,因为类方法在元类中都是以对象方法的形式存在的;那么如何在元类中实现一个对象方法呢?只需要在当前类中实现类方法resolveClassMethod即可:
然后,按照resolveInstanceMethod的实现,来填充resolveClassMethod方法:
需要注意的是,因为虽然都是在Teacher类中实现的方法,但是最终resolveClassMethod方法需要对应到元类上,所以我们添加的临时实现方法也要添加到元类上;
但是为什么resolveClassMethod方法被执行了多次呢?
分析源码发现,调用了resolveClassMethod方法之后,在下边判断了当前类是否实现resolveClassMethod方法,如果实现了,会调用resolveInstanceMethod方法,因为类方法在元类中是以对象方法的形式存在的,同样会去元类中查找是否有相应的对象方法,根据之前我们分析的isa走位图,所以此方法会执行多次;
那么,既然最终找的都是对象方法,那么可不可以将resolveInstanceMethod和resolveClassMethod的实现合二为一呢?
新建一个NSObject的分类,将resolveInstanceMethod和resolveClassMethod的实现放在分类中,因为resolveClassMethod最终调用的也是resolveInstanceMethod,所以我们只用实现resolveInstanceMethod方法即可
这样,我们就实现了在NSObject的分类中监听未实现的方法的目的;这其实就是面向切面编程也就是我们常说的AOP;