问题
7 月 8 日 iOS 14 beta 2 放出后,我们注意到一个 crash 激增了起来。
这个 crash 顶部的堆栈为:
0 _objc_retain (in libobjc.A.dylib)
1 -[UIInputResponderController prepareToMoveKeyboardForInputViewSet:animationStyle:] (in UIKitCore)
2 -[UIInputResponderController setKeyWindowSceneInputViews:animationStyle:] (in UIKitCore)
3 -[UIInputResponderController setInputViews:animationStyle:] (in UIKitCore)
4 -[UIInputResponderController setInputViews:animated:] (in UIKitCore)
5 -[UIInputResponderController setInputViews:] (in UIKitCore)
......
并且我们注意到,这个问题的触发,和业务形态没有特别密切的联系,多个 app 都遇到了这个崩溃,且量级不低。
修复方案
先抛出一下我们最后确定的修复方案:
hook 私有方法 -[UIInputViewSet restorableResponder]
,直接返回 nil
。
由于是系统库自身的问题,我们没有源码,很难保证这个修复没有引入新的坑。但从目前的测试结果来看,至少崩溃不再复现了,并且看起来有关联的键盘场景,也没有严重问题。
原因定位
造成 crash 的原因,是系统私有类 UIInputViewSet
中的 restorableResponder
属性,既不是 weak 也不是 strong,类似于 unsafe_unretained。所以当它被访问时,很容易造成野指针。当它被赋值给一个 __strong id
类型的变量时,则会在 _objc_retain
中崩溃。
我们可以打符号断点,从汇编中确认, iOS 14 beta 2 中,restorableResponder 属性的 getter 和 setter 方法,只是存取了一个内存值,没有做任何 weak 或 strong 应有的操作。
(从汇编指令看,没有 storeWeak 或 storeStrong 操作)
可以 hook -[UIInputViewSet restorableResponder]
方法,验证一下它的返回值是不是经常是个野指针。
定位过程
是怎么定位到 -[UIInputViewSet restorableResponder]
方法的呢?我的思路是这样的:
1、猜测问题来源于最顶栈 -[UIInputResponderController prepareToMoveKeyboardForInputViewSet:animationStyle:] (in UIKitCore)
的参数
2、hook -[UIInputResponderController prepareToMoveKeyboardForInputViewSet:animationStyle:]
方法,获取传入的参数,发现有两个参数,第一个参数类型是 UIInputViewSet,这两个参数本身不是野指针。
3、hook -[UIInputResponderController prepareToMoveKeyboardForInputViewSet:animationStyle:]
,尝试将传入的两个参数改成 nil,发现 crash 不复现了。这基本可以确认问题出在参数上。但参数本身不是野指针,所以推测问题出现在参数某个方法的返回值上。
4、根据一份旧的 UIInputViewSet 头文件,在 lldb 中依次对这个 UIInputViewSet 对象发送消息。发现其中有个属性 restorableResponder,在旧版本上标记为 weak,但 getter 方法 return 出来的地址不是一个对象。严重怀疑这是一个野指针。
5、hook -[UIInputViewSet restorableResponder]
,返回 nil,crash 不复现,确认问题出在 -[UIInputViewSet restorableResponder]
方法上
6、通过汇编确认 restorableResponder
在 iOS 14 beta 2 上既不是 strong 也不是 weak
相信很多团队都遇到了这个 crash,先前在网络上搜索未果,暂且抛出我的解决方案,欢迎与大家交流。
当然最终还是等苹果爸爸真正修复它 😭