前言
上篇文章说了方法的查找流程,那么查找不到的情况呢?对,就是报错,程序崩溃。我们来简单的复现一下,我们声明一个对象方法和一个类方法但是不实现,如图:
调用对象方法,如图:
调用类方法,如图:
以上2个未实现的方法调用均引起了程序的崩溃,分别是:
- -[Person sayWork]: unrecognized selector sent to instance 0x108b27bc0
- +[Person sayHappy]: unrecognized selector sent to class 0x100008208 如何避免这种方法未实现引起的崩溃呢?我们继续探索。
一、崩溃的根源
回顾上篇,在lookUpImpOrForward中,当curClass为空时候imp = forward_imp;且跳出循环,如图:
此时返回的为
forward_imp。在lookUpImpOrForward开始时候我们定义了forward_imp,如图:
所以我们找到了
_objc_msgForward_impcache这个imp,即:当imp找不到时,系统会调用_objc_msgForward_impcache。
进入_objc_msgForward_impcache,发现找不到实现,如图:
全局搜索,在汇编中找到实现,如图:
经过简单的流程找到了
__objc_forward_handler,再次查找,实现如图:
所以报错的根源就找到了。(这里只是流程,真实的报错在CF库里)
二、动态方法决议
要避免这种报错,使我们的程序更加健壮,我们必须把这种报错处理了,iOS的系统也不会这么死板,所以我们得找到这种报错的处理方式。回到lookUpImpOrForward中,我们不难找出在循环结束之后的一些处理,如图:
这是一个一次性方法,在第一次没找到的时候会进入到该判断,然后修改
behavior值保证下一次进入该判断时候判断条件不满足。
证明:
在汇编跳转lookUpImpOrForward时,x3的值为3,即lookUpImpOrForward的第三个参数为3,如图:
在
lookUpImpOrForward中查看behavior的值也是3,如图:
LOOKUP_RESOLVER的值为2,如图:
所以第一次判断为
3&2,即11&10=10值为2为真,然后behavior ^= LOOKUP_RESOLVER;,behavior的值为11^10为01,再作为参数传入resolveMethod_locked,后面会通过lookUpImpOrForwardTryCache和_lookUpImpTryCache再次调用lookUpImpOrForward并传入behavior,此次判断的条件为1&2,即01&10值为假 不满足条件。
所以没有找到实现之后在返回_objc_msgForward_impcache之前我们系统进行了其他的容错处理,而这个处理就是resolveMethod_locked,接下来我们进行深入探究。resolveMethod_locked的实现如图:
先看返回,根据返回我们进入
lookUpImpOrForwardTryCache,如图:
再
_lookUpImpTryCache,如图:
因为在
msgSend开始验证过cls,所以必然会满足该判断条件重新进入lookUpImpOrForward。那么这个方法就转了一圈回来继续查找么?很显然肯定不会这样浪费资源的,所以我们回到resolveMethod_locked,入上图所示,在返回lookUpImpOrForwardTryCache之前有个判断,分支内的方法为resolveInstanceMethod和resolveClassMethod。
resolveInstanceMethod实现如图:
如图标注,判断当前类中有实现
resolveInstanceMethod则进行调用并传入当前的sel,这里主要的事情已经处理过了。
resolveClassMethod如图:
比实例方法多了一点计算,因为类方法储存在元类中,所以我取到元类。
小结
经过前面的探究,我们明白在类中实现resolveInstanceMethod和resolveClassMethod方法可以拦截到当前即将崩溃的信息。
我们在实现中实现resolveInstanceMethod,并调用未实现的sayWork,如图:
我们走到了
resolveInstanceMethod中点断点,并且打印出了sayWork,所以我们成功地在报错前拦截到了。接下来在这里动态插入一个imp与sayWork对应,就可以不报错了,操作如下图:
我们成功地来到
sayWork,并把sayOk的imp与sayWork的sel关联。继续往下走,如图:
成功的打印出了
sayOk。
同理resolveClassMethod如图:
所以我们通过动态方法决议成功地处理了方法未实现的崩溃。