iOS底层原理10:消息动态决议

unrecognized selector的底层实现原理

创建类PersonTeacher;

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,而$0x17,我们根据代码可以分析出__objc_forward_handler操作之后,将结果给了x17__objc_forward_handler才是我们需要重点研究的地方:

__OBJC2__中,__objc_forward_handler对应赋值的是objc_defaultForwardHandler,而在objc_defaultForwardHandler的实现中,最终打印了**** unrecognized selector sent to instance ***

虽然我们找到了最终输出错误的地方,但是依然没有解决如何处理这个错误的问题,要想处理这个错误,那么就需要了解消息的处理流程

消息处理流程图

对象方法动态决议

接下来,我们结合源码分析talk方法的执行:

确定执行Teachertalk方法时,进入lookUpImpOrForward方法内部,因为talk方法没有实现,所以代码将会执行到:

behavior是方法传进来的参数,从汇编源码中我们可以看到,它的值为3

与打印结果一致:

behavior & LOOKUP_RESOLVER3 & 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走位图,所以此方法会执行多次;

那么,既然最终找的都是对象方法,那么可不可以将resolveInstanceMethodresolveClassMethod的实现合二为一呢?

新建一个NSObject的分类,将resolveInstanceMethodresolveClassMethod的实现放在分类中,因为resolveClassMethod最终调用的也是resolveInstanceMethod,所以我们只用实现resolveInstanceMethod方法即可

这样,我们就实现了在NSObject的分类中监听未实现的方法的目的;这其实就是面向切面编程也就是我们常说的AOP;

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享