动态方法决议

350 阅读3分钟

前言

上篇文章说了方法的查找流程,那么查找不到的情况呢?对,就是报错,程序崩溃。我们来简单的复现一下,我们声明一个对象方法和一个类方法但是不实现,如图: 截屏2021-10-27 上午10.56.33.png 调用对象方法,如图: 截屏2021-10-27 上午10.58.20.png调用类方法,如图: 截屏2021-10-27 上午10.59.19.png 以上2个未实现的方法调用均引起了程序的崩溃,分别是:

  • -[Person sayWork]: unrecognized selector sent to instance 0x108b27bc0
  • +[Person sayHappy]: unrecognized selector sent to class 0x100008208 如何避免这种方法未实现引起的崩溃呢?我们继续探索。

一、崩溃的根源

回顾上篇,在lookUpImpOrForward中,当curClass为空时候imp = forward_imp;且跳出循环,如图: imp.png 此时返回的为forward_imp。在lookUpImpOrForward开始时候我们定义了forward_imp,如图: forward.png 所以我们找到了_objc_msgForward_impcache这个imp,即:imp找不到时,系统会调用_objc_msgForward_impcache

进入_objc_msgForward_impcache,发现找不到实现,如图: 截屏2021-12-08 下午2.45.54.png 全局搜索,在汇编中找到实现,如图: objc_msgForward.png 经过简单的流程找到了__objc_forward_handler,再次查找,实现如图: objc_defaultForwardHandler.png 所以报错的根源就找到了。(这里只是流程,真实的报错在CF库里)

二、动态方法决议

要避免这种报错,使我们的程序更加健壮,我们必须把这种报错处理了,iOS的系统也不会这么死板,所以我们得找到这种报错的处理方式。回到lookUpImpOrForward中,我们不难找出在循环结束之后的一些处理,如图: 20211208154409.jpg 这是一个一次性方法,在第一次没找到的时候会进入到该判断,然后修改behavior值保证下一次进入该判断时候判断条件不满足。

证明: 在汇编跳转lookUpImpOrForward时,x3的值为3,即lookUpImpOrForward的第三个参数为3,如图: 20211208160459.jpglookUpImpOrForward中查看behavior的值也是3,如图: 20211208160724.jpg LOOKUP_RESOLVER的值为2,如图: 20211208160958.jpg 所以第一次判断为3&2,即11&10=10值为2为真,然后behavior ^= LOOKUP_RESOLVER;behavior的值为11^1001,再作为参数传入resolveMethod_locked,后面会通过lookUpImpOrForwardTryCache_lookUpImpTryCache再次调用lookUpImpOrForward并传入behavior,此次判断的条件为1&2,即01&10值为假 不满足条件。

所以没有找到实现之后在返回_objc_msgForward_impcache之前我们系统进行了其他的容错处理,而这个处理就是resolveMethod_locked,接下来我们进行深入探究。resolveMethod_locked的实现如图: 20211208170355.jpg 先看返回,根据返回我们进入lookUpImpOrForwardTryCache,如图: 20211208170643.jpg_lookUpImpTryCache,如图: 20211208170505.jpg 因为在msgSend开始验证过cls,所以必然会满足该判断条件重新进入lookUpImpOrForward。那么这个方法就转了一圈回来继续查找么?很显然肯定不会这样浪费资源的,所以我们回到resolveMethod_locked,入上图所示,在返回lookUpImpOrForwardTryCache之前有个判断,分支内的方法为resolveInstanceMethodresolveClassMethod

resolveInstanceMethod实现如图: 20211208172238.jpg 如图标注,判断当前类中有实现resolveInstanceMethod则进行调用并传入当前的sel,这里主要的事情已经处理过了。

resolveClassMethod如图: 20211208173301.jpg 比实例方法多了一点计算,因为类方法储存在元类中,所以我取到元类。

小结

经过前面的探究,我们明白在类中实现resolveInstanceMethodresolveClassMethod方法可以拦截到当前即将崩溃的信息。

我们在实现中实现resolveInstanceMethod,并调用未实现的sayWork,如图: 20211208175420.jpg 我们走到了resolveInstanceMethod中点断点,并且打印出了sayWork,所以我们成功地在报错前拦截到了。接下来在这里动态插入一个impsayWork对应,就可以不报错了,操作如下图: 20211208180756.jpg 我们成功地来到sayWork,并把sayOkimpsayWorksel关联。继续往下走,如图:

20211208181158.jpg 成功的打印出了sayOk

同理resolveClassMethod如图: 20211208182105.jpg 所以我们通过动态方法决议成功地处理了方法未实现的崩溃。