前言
上篇文章说了方法的查找流程,那么查找不到的情况呢?对,就是报错,程序崩溃。我们来简单的复现一下,我们声明一个对象方法和一个类方法但是不实现,如图:
调用对象方法,如图:
调用类方法,如图:
以上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
如图:
所以我们通过动态方法决议成功地处理了方法未实现的崩溃。