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
;