OC的消息转发

OC的消息发送objc_msgSend 中,我们说到了,对象调用方法,如果这个方法没有被找到的话,就会进入动态决议与消息转发阶段。那么今天我们就来研究下这个阶段。

动态决议

先看代码,在lookUpImpOrForward方法中,查找方法过程完毕后,即在NSObject类中查找完,都没有找到方法的话,会走到这里:

图片.png

forward_imp是lookUpImpOrForward方法最开始创建的:

图片.png

我们看下_objc_msgForward_impcache方法。该方法是汇编写的,其中_objc_forward_handler是重点。

图片.png

图片.png

看到“unrecognized selector sent to instance...”这一段,感觉熟悉吗?这就是平时我们调一个未实现的方法,报错的时候打印的信息。

继续往下走,会走到resolveMethod_locked方法:

图片.png

图片.png

resolveInstanceMethod方法:

图片.png

resolveClassMethod方法:

图片.png

从resolveMethod_locked方法里的逻辑是,要对类进行是否为元类的判断

  • 如果我们只是普通的一个对象调用一个方法,会走到resolveInstanceMethod方法中,在这里给对象对应的类发送了一个消息让它去执行resolveInstanceMethod:方法;

  • 如果是调一个类方法,会走到了resolveClassMethod方法里,这个方法的参数cls就是类的元类,而通过nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst)这一句,就找到了当前类nonmeta,之后给nonmeta发送了一个消息让它执行resolveClassMethod:方法。

综上,我们以Person类为例来说明,不管我们是用Person的一个对象p调一个实例方法,还是直接用Person调一个类方法,如果没找到对应的方法,我们可以直接在Person类里面添加resolveInstanceMethod:类方法或者resolveClassMethod:类方法,在这两个方法中,我们动态的加入要执行的方法,这样就不会发生崩溃了。这就是动态决议

如下图,执行[XXPerson say],say是个类方法,但是没有在类中实现,我们就在resolveClassMethod:方法中动态添加了一个方法,那么它就会走到method2:

图片.png

消息转发

我们知道,在lookUpImpOrForward方法中,当查找到方法时,会走“goto done”,在done里面调了log_and_fill_cache方法,具体内容如下:

图片.png

运行发现,代码没有走到logMessageSend方法里去,因为判断条件中的objcMsgLogEnabled为false。如果能走到logMessageSend方法,就会走到下面这段代码,因为objcMsgLogFD是等于-1,这段代码涉及到tmp文件夹下的文件。

图片.png

经过查询发现,objcMsgLogEnabled默认就是false,而在代码中,也只有一个方法能将它的值改为true,这个方法就是instrumentObjcMessageSends(BOOL flag)。那我们就手动调用这个方法。

新建一个工程,在类中实现test方法。

图片.png

运行完毕之后发现,电脑tmp文件夹下生成了一个"msgSends"开头的文件,里面有我们刚才person对象调用的test方法:

图片.png

这是在能找到test方法的情况下生成的文件,如果没找到方法呢?我们把test的实现注释掉,再试一下。

图片.png

这一次也生成了一个新的msgSends文件,里面内容很多,前面几个方法,除了上文中说到的resolveInstanceMethod:,还有forwardingTargetForSelector:methodSignatureForSelector:

这两个方法就是消息转发,我们在类中添加这两个方法,并把代码中instrumentObjcMessageSends(YES)和instrumentObjcMessageSends(NO)两行去掉。

运行后代码会走到forwardingTargetForSelector方法。forwardingTargetForSelector,翻译过来,就是把要执行的selector转发给其它对象,因为在XXPerson类里没有实现test方法,而XXTeacher类实现了该方法,这里便转发给了一个XXTeacher对象,最后就会执行XXTeacher的test方法。

图片.png

如果类中没有实现forwardingTargetForSelector方法,就走到了消息的慢速转发,就会走methodSignatureForSelector方法,不过它要和forwardInvocation:方法配套使用。methodSignatureForSelector要返回一个方法签名

图片.png

我们可以将这两个方法写在NSObject的一个分类里,避免因为方法找不到而发生崩溃。

至此,动态决议和消息转发的基本流程就讲完了,这个流程给了开发者三次避免因方法找不到而发生崩溃的机会。

猜你喜欢

转载自juejin.im/post/7109840649280552974