You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:
前言
在开发中
unrecognized selector sent to instance XXXXX
是非常常见的 crash 类型。例如调用以下一段代码就会产生crash
具体 crash 时的表现见下:
这类 crash 尤其在混合开发,或者 JS 与 native 交互中经常遇到,非常影响用户体验,也降低了 app 的质量与稳定性。
常见的 crash 场景可以总结为:
在研究如何实现 app 自修复该 bug 前,我们可以研究下什么时候会报 unrecognized selector 的异常?
什么时候会报unrecognized selector的异常?
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常
unrecognized selector sent to XXX
。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:objc运行时会调用
+resolveInstanceMethod:
或者+resolveClassMethod:
,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。如果目标对象实现了
-forwardingTargetForSelector:
,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。
这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
3. Normal forwarding
这一步是Runtime最后一次给你挽救的机会。首先它会发送
-methodSignatureForSelector:
消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:
返回nil,Runtime则会发出-doesNotRecognizeSelector:
消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:
消息给目标对象。拦截调用的整个流程即 Objective-C 的消息转发机制。其具体流程如下图:
unrecognized selector sent to instance XXXXX
crash 自修复技术实现原理简单来说:
可以利用消息转发机制的三个步骤,选择哪一步去改造比较合适呢?
这里我们选择了第二步
forwardingTargetForSelector
。引用 《大白健康系统--iOS APP运行时Crash自动修复系统》 的分析:resolveInstanceMethod
需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余的forwardInvocation
可以通过NSInvocation
的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation
对象,并且forwardInvocation
的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写forwardingTargetForSelector
可以将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写选择了
forwardingTargetForSelector
之后,可以将NSObject
的该方法重写,做以下几步的处理:具体如下:
forwardingTargetForSelector
方法添加白名单,避免出现hook内部方法以及不必要的对象。
内部对象的特征是都以
_
开头。其他需要限制的部分,经常会出现在组件化开发、SDK开发中,避免影响到其他模块的正常工作,可以用类的前缀做区分。
其中动态创建的方法,返回值为什么返回一个 NSNull,而不是其他的值。
这样做的好处在于,在设置白名单的时候,只需要将 NSNull 设置进白名单,就可以解决方法返回值调用方法造成的crash。返回其他类型,就需要在白名单中多设置一种类型。
可以解决如下问题:
hook时注意如果对象的类本身如果重写了
forwardInvocation
方法的话,就不应该对forwardingTargetForSelector
进行重写了,否则会影响到该类型的对象原本的消息转发流程。通过重写
NSObject
的forwardingTargetForSelector
方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象中,从而可以使app继续正常运行。具体的实现代码如下:
参考文献:
Posted by Posted by 微博@iOS程序犭袁 & 公众号@iTeaTime技术清谈
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
The text was updated successfully, but these errors were encountered: