objc_msgSend-消息转发

2,210 阅读6分钟

接上节,我们在调用方法的时候就是再给某个对象发送消息,在编译的阶段,编译器就会把这个方法转换成objc_msgSend()函数,objc_msgSend()函数有两个参数:消息的接收者和消息的方法名,通过这两个参数就能找到对应的方法,如果在本类中没有找到就会继续在父类中找,如果在父类中还是没有找到对应的方法,那么就执行消息转发 图片.png forward_imp是通过_objc_msgForward_impcache函数生成的,_objc_msgForward_impcache函数是用汇编写的

const IMP forward_imp = (IMP)_objc_msgForward_impcache; 图片.png

我们看__objc_forward_handler是做了方法找不到时的处理,“+”和“-”这里没有区分是实例方法和类方法 图片.png

我们在这里看到他只是给imp进行赋值,并没有调用imp, 所以在没有找到方法的报错之前是得把lookUpImpOrForward()这个方法的流程先走完 图片.png

一、消息动态决议

1.当我们在lookUpImpOrForward()中的for循环中没有找到方法的时候,对IMP赋值后还会有这个流程,(注释:当没有找到这个方法的实现,就会尝试解析方法一次)

这里如果没有找到方法实现,就会尝试解析方法,只会调用一次 图片.png 我们来验证一下,我们创建一个类,然后再main中调用一个没有实现的实例方法 图片.png 我们发现它走到了这里,behavior = 3(初始值就是3),LOOKUP_RESOLVER = 2,3&2=2 ,进入这个判断,3(011)^2(010)=1,此时behavior就被赋值为1了,然后调用resolveMethod_locked()方法 图片.png 这里先进行了动态决议,在动态决议之后他还调用了下面的lookUpImpOrForwardTryCache()方法,我们先看lookUpImpOrForwardTryCache()方法 图片.png 这里传过来的behavior就是3 图片.png 我们先来看下面的lookUpImpOrForwardTryCache()->lookUpImpOrForwardTryCache()->_lookUpImpTryCache() 在进行方法的动态决议之后,系统还是会在cache中再找一遍方法,如果没找到还是会在掉一遍lookUpImpOrForward()方法,这时候掉的lookUpImpOrForward()的behavior就是1了,就不会继续往下走了 图片.png 图片.png

2.前面我们说的动态决议的两个方法resolveClassMethod(),resolveInstanceMethod(),系统会去调用的两个方法,我们可以在类中重写这两方法,来为我们没有实现的方法通过runtime的API动态的给方法添加实现 图片.png

3.resolveInstanceMethod()就是用objc_msgSend去调用resolveInstanceMethod:这个方法,我们来实现一下,我们看到当没有找到方法的实现时执行到了这里,然后调用了resolveInstanceMethod:这个方法,打印了两次(为什么会调用两次后面补充) 图片.png 方法找不到实现就是sel找不到imp,那我们可以在这个方法中添加runtimeAPI动态的给sel添加imp,test方法没有实现的时候就是使用了runtimeAPI动态添加了方法的实现就是method1方法 图片.png 当我们动态的添加了resolveInstanceMethod()方法的时候,系统还是会调用lookUpImpOrNilTryCache()方法,去找IMP 图片.png 这个时候这个imp会不会被缓存呢,就是说怎么做test这个方法不会被扩容,我们验证一下 图片.png 图片.png 通过获取cache里的内容我们找到了test方法,也就是说当方法进行动态决议的时候是会被缓存的

4.resolveClassMethod() 当我们类方法找不到的时候会调用resolveClassMethod()方法 图片.png 当我们把resolveInstanceMethod()方法和resolveClassMethod()方法的判断去掉时,传入一个没有实现的方法,然后发现调用了method1()方法,这是为什么呢 图片.png 图片.png 当类方法在动态决议时,如果没有找到实现他还会调用resolveInstanceMethod()方法,所以还会调用method1()方法 图片.png 5.

resolveInstanceMethod()方法应该写在类里面

resolveClassMethod()方法应该写在元类里面

如果系统没有提供resolveClassMethod()这个方法时,类方法的动态决议应该怎么处理呢

我们应该将resolveInstanceMethod()方法写在NSObject中来使用,由于比较繁琐,系统才提供了resolveClassMethod()这个方法,是系统为了简化类方法查找流程提供的,最后都要通过resolveInstanceMethod()方法

6.下面这个例子,在元类中找方法的实现找不到,就要动态决议,会一直循环找 图片.png 7.当我们在NSObject分类中写上这个方法,不管是实例方法还是类方法找不到的崩溃就不会出现了,有利于程序的稳定,这么写类似aop(面向切面对象:在不修改源代码的情况下,通过运行时来给程序添加统一的功能,用来进行埋点) 图片.png

二、消息转发

当方法找到之后就会执行done, 图片.png 在done中还执行log_and_fill_cache()这个方法 图片.png 我们进入方法看到 图片.png objcMsgLogEnabled默认值是false,我们看哪里设置了这个值 图片.png 查找后发现只有这个instrumentObjcMessageSends()方法对这个值进行赋值, 图片.png 那么我们来创建个工程来写一下这个方法,这里test方法是已经实现的 图片.png 我们根据这个路径进行前往文件夹 图片.png 就会看到一个msgSends-xxxx的文件,打开就会看到调用的方法 图片.png 当我们不去实现test()方法的时候我们再来运行一次(要先删除msgSends-xxxx文件再运行),崩溃,根据上面的方法找到msgSends-xxxx文件,我们看到很多日志,这里是在动态决议之后和崩溃之前进行了这两个方法的操作,这两个放大就是消息的转发 图片.png

消息的转发是在动态决议之后

1.消息的快速转发 当我们调用的方法没有实现,也没有进行动态决议的时候他就会进入

  • (id)forwardingTargetForSelector:(SEL)aSelector这个方法,这就是消息转发的入口,又叫做消息的快速转发 当对象响应不了某个方法,就在这个方法中将方法转发出去,指定这个类中不能响应的方法 图片.png

这个时候method1的方法时缓存在LGTeacher类中 类方法的消息转发 图片.png

2.消息的慢速转发 当我们调用的方法没有实现,没有进行动态决议、也没有进行快速转发的时候,就回去进入 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个方法,这就叫做消息的慢速转发,他需要返回一个方法的有效签名(只要是正确的格式就可以),当返回一个方法签名后,系统会搭配一个forwardInvocation:(NSInvocation *)anInvocation 方法使用,这么写程序就不会崩溃了 图片.png 也可以在这个方法里进行方法的转发,首先是在自己的类中找方法的实现,找不到就转发的别的类中 图片.png

这是消息流程图,在类或父类中找不到方法的实现->动态决议->消息快速转发->消息慢速转发(是否选择有效选择器)->抛出异常 图片.png

三、补充

1.resolveInstanceMethod:这个方法打印了两次,这是什么原因呢

第一次进来的时候是方法快速查找查找不到的时候就会调用_objc_msgSend_unchached,然后调用lookUpImpOrForward(),然后进行方法的动态决议,这是第一次进入resolveInstanceMethod:()方法, 图片.png 第二次进入的时候这个堆栈信息变了 图片.png 我们在控制台输入bt,来看一下 如果方法找不到他没有在动态决议中处理也没有进行快速转发,那么系统会在慢速转发的方法中调用class_getInstanceMethod() 图片.png 我们找到这个class_getInstanceMethod()方法的实现,在这个方法的实现中他又调用了一遍lookUpImpOrForward(),在调用这个方法的时候就会再次进行动态决议 图片.png