接上节,我们在调用方法的时候就是再给某个对象发送消息,在编译的阶段,编译器就会把这个方法转换成objc_msgSend()函数,objc_msgSend()函数有两个参数:消息的接收者和消息的方法名,通过这两个参数就能找到对应的方法,如果在本类中没有找到就会继续在父类中找,如果在父类中还是没有找到对应的方法,那么就执行消息转发
forward_imp是通过_objc_msgForward_impcache函数生成的,_objc_msgForward_impcache函数是用汇编写的
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
我们看__objc_forward_handler是做了方法找不到时的处理,“+”和“-”这里没有区分是实例方法和类方法
我们在这里看到他只是给imp进行赋值,并没有调用imp,
所以在没有找到方法的报错之前是得把lookUpImpOrForward()这个方法的流程先走完
一、消息动态决议
1.当我们在lookUpImpOrForward()中的for循环中没有找到方法的时候,对IMP赋值后还会有这个流程,(注释:当没有找到这个方法的实现,就会尝试解析方法一次)
这里如果没有找到方法实现,就会尝试解析方法,只会调用一次
我们来验证一下,我们创建一个类,然后再main中调用一个没有实现的实例方法
我们发现它走到了这里,behavior = 3(初始值就是3),LOOKUP_RESOLVER = 2,3&2=2 ,进入这个判断,3(011)^2(010)=1,此时behavior就被赋值为1了,然后调用resolveMethod_locked()方法
这里先进行了动态决议,在动态决议之后他还调用了下面的lookUpImpOrForwardTryCache()方法,我们先看lookUpImpOrForwardTryCache()方法
这里传过来的behavior就是3
我们先来看下面的lookUpImpOrForwardTryCache()->lookUpImpOrForwardTryCache()->_lookUpImpTryCache() 在进行方法的动态决议之后,系统还是会在cache中再找一遍方法,如果没找到还是会在掉一遍lookUpImpOrForward()方法,这时候掉的lookUpImpOrForward()的behavior就是1了,就不会继续往下走了
![]()
2.前面我们说的动态决议的两个方法resolveClassMethod(),resolveInstanceMethod(),系统会去调用的两个方法,我们可以在类中重写这两方法,来为我们没有实现的方法通过runtime的API动态的给方法添加实现
3.resolveInstanceMethod()就是用objc_msgSend去调用resolveInstanceMethod:这个方法,我们来实现一下,我们看到当没有找到方法的实现时执行到了这里,然后调用了resolveInstanceMethod:这个方法,打印了两次(为什么会调用两次后面补充)
方法找不到实现就是sel找不到imp,那我们可以在这个方法中添加runtimeAPI动态的给sel添加imp,test方法没有实现的时候就是使用了runtimeAPI动态添加了方法的实现就是method1方法
当我们动态的添加了resolveInstanceMethod()方法的时候,系统还是会调用lookUpImpOrNilTryCache()方法,去找IMP
这个时候这个imp会不会被缓存呢,就是说怎么做test这个方法不会被扩容,我们验证一下
通过获取cache里的内容我们找到了test方法,也就是说当方法进行动态决议的时候是会被缓存的
4.resolveClassMethod()
当我们类方法找不到的时候会调用resolveClassMethod()方法
当我们把resolveInstanceMethod()方法和resolveClassMethod()方法的判断去掉时,传入一个没有实现的方法,然后发现调用了method1()方法,这是为什么呢
当类方法在动态决议时,如果没有找到实现他还会调用resolveInstanceMethod()方法,所以还会调用method1()方法
5.
resolveInstanceMethod()方法应该写在类里面
resolveClassMethod()方法应该写在元类里面
如果系统没有提供resolveClassMethod()这个方法时,类方法的动态决议应该怎么处理呢
我们应该将resolveInstanceMethod()方法写在NSObject中来使用,由于比较繁琐,系统才提供了resolveClassMethod()这个方法,是系统为了简化类方法查找流程提供的,最后都要通过resolveInstanceMethod()方法
6.下面这个例子,在元类中找方法的实现找不到,就要动态决议,会一直循环找
7.当我们在NSObject分类中写上这个方法,不管是实例方法还是类方法找不到的崩溃就不会出现了,有利于程序的稳定,这么写类似aop(面向切面对象:在不修改源代码的情况下,通过运行时来给程序添加统一的功能,用来进行埋点)
二、消息转发
当方法找到之后就会执行done,
在done中还执行log_and_fill_cache()这个方法
我们进入方法看到
objcMsgLogEnabled默认值是false,我们看哪里设置了这个值
查找后发现只有这个instrumentObjcMessageSends()方法对这个值进行赋值,
那么我们来创建个工程来写一下这个方法,这里test方法是已经实现的
我们根据这个路径进行前往文件夹
就会看到一个msgSends-xxxx的文件,打开就会看到调用的方法
当我们不去实现test()方法的时候我们再来运行一次(要先删除msgSends-xxxx文件再运行),崩溃,根据上面的方法找到msgSends-xxxx文件,我们看到很多日志,这里是在动态决议之后和崩溃之前进行了这两个方法的操作,这两个放大就是消息的转发
消息的转发是在动态决议之后
1.消息的快速转发 当我们调用的方法没有实现,也没有进行动态决议的时候他就会进入
- (id)forwardingTargetForSelector:(SEL)aSelector这个方法,这就是消息转发的入口,又叫做消息的快速转发
当对象响应不了某个方法,就在这个方法中将方法转发出去,指定这个类中不能响应的方法
这个时候method1的方法时缓存在LGTeacher类中 类方法的消息转发
2.消息的慢速转发
当我们调用的方法没有实现,没有进行动态决议、也没有进行快速转发的时候,就回去进入
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个方法,这就叫做消息的慢速转发,他需要返回一个方法的有效签名(只要是正确的格式就可以),当返回一个方法签名后,系统会搭配一个forwardInvocation:(NSInvocation *)anInvocation 方法使用,这么写程序就不会崩溃了
也可以在这个方法里进行方法的转发,首先是在自己的类中找方法的实现,找不到就转发的别的类中
这是消息流程图,在类或父类中找不到方法的实现->动态决议->消息快速转发->消息慢速转发(是否选择有效选择器)->抛出异常
三、补充
1.resolveInstanceMethod:这个方法打印了两次,这是什么原因呢
第一次进来的时候是方法快速查找查找不到的时候就会调用_objc_msgSend_unchached,然后调用lookUpImpOrForward(),然后进行方法的动态决议,这是第一次进入resolveInstanceMethod:()方法,
第二次进入的时候这个堆栈信息变了
我们在控制台输入bt,来看一下
如果方法找不到他没有在动态决议中处理也没有进行快速转发,那么系统会在慢速转发的方法中调用class_getInstanceMethod()
我们找到这个class_getInstanceMethod()方法的实现,在这个方法的实现中他又调用了一遍lookUpImpOrForward(),在调用这个方法的时候就会再次进行动态决议