美文网首页
分析一次比较奇怪的crash

分析一次比较奇怪的crash

作者: Jason1226 | 来源:发表于2024-01-02 15:59 被阅读0次

客户反馈在使用aspects hook UIView的setBackgroundColor:方法后,打开在线pdf的webview后打开我们的webview会crash。webview内有对webview.scrollView添加backgroundColor的监听(addObserver)。
报错信息如下:

2024-01-03 14:37:42.486091+0800 test4[9534:929623] -[WKScrollView _original_setBackgroundColor:]: unrecognized selector sent to instance 0x12c09f800

添加条件断点,查看调用堆栈。
-[NSObject(NSObject) doesNotRecognizeSelector:]


截屏2024-01-03 14.39.47.png

比较奇怪,
1._original_setBackgroundColor:,方法哪里来的?
2.调用栈并不是一个正常的KVO调用栈。
3.为什么先打开pdf就会闪退,不打开则正常?

整理下思路:
aspects hook了hook UIView的setBackgroundColor:方法。也就是UIView类对象的setBackgroundColor: 方法交换了_objc_msgForward,在消息转发第三步调用了forwardInvocation,并调用了ASPECTS_ARE_BEING_CALLED函数。
unrecognized selector方法是在消息转发三步骤都没找到,doesNotRecognizeSelector:内抛出的crash。
所以在ASPECTS_ARE_BEING_CALLED函数内加上断点,试图捕获crash信息。但是测试发现,webview.scrollView.backgroundColor = xxx,只有最后那次crash时未进来,其它每次都正常进入。
问题:doesNotRecognizeSelector是从哪里抛出?

思考:
添加KVO后,NSKVONotifying_WKScrollView重写了setBackgroundColor:方法,内部调用了super setBackgroundColor:。
webview.scrollView.backgroundColor = xxx执行流程依次是:
1.NSKVONotifying_WKScrollView setBackgroundColor
2.UIView setBackgroundColor
3._objc_msgForward
4.ASPECTS_ARE_BEING_CALLED

但事实是没进入ASPECTS_ARE_BEING_CALLED函数。

输出下NSKVONotifying_WKScrollView的所有方法名,发现了一个奇怪的方法,_original_setContentInset:,前缀和_original_setBackgroundColor。
查找资料,找到这篇文章:
https://mp.weixin.qq.com/s/bHdtetOb3LQGRfRO9AFVQA
大概意思就是KVO的底层机制也分场景,像contentInset这种是另一种机制,会直接调用_CF_forwarding_prep_0进入消息转发。并且会添加一个original前缀的方法到NSKVONotifying_XX类中,指向原始的IMP。
_CF_forwarding_prep_0在前文的调用栈截图也看到了。

然后我测试加载pdf后会有_original_setContentInset,不加载则没有。
猜测加载pdf内部添加了KVO给contentInset。

进一步验证,不打开pdf,但后续打开webview添加以下KVO:

    [self.webView.scrollView addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
    [self.webView.scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];

会生成_original_setContentInset方法,且会crash。

    [self.webView.scrollView addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
    //[self.webView.scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];

不会生成_original_setContentInset方法,不会crash。

    //[self.webView.scrollView addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
    [self.webView.scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];

会生成_original_setContentInset方法,不会crash。
验证了结论。

backgroundColor在KVO时不会添加_original_setbackgroundColor方法,但是却因为aspects的影响,导致backgroundColor在NSKVONotifying_WKScrollView在重写时生成了类似contentInset的实现:
原本应当是:

- (void)setBackgroundColor:(UIColor)color {
  [self willChangeValueForKey:@"backgroundColor"];
  [super setBackgroundColor:color];
  [self didChangeValueForKey:@"backgroundColor"];
}

但因为Aspects影响,错误处理成了。

- (void)setBackgroundColor:(UIColor)color {
    _CF_forwarding_prep_0;
}

_CF_forwarding_prep_0最终在消息转发步骤3,调用了带上了original前缀的方法。

相关文章

网友评论

      本文标题:分析一次比较奇怪的crash

      本文链接:https://www.haomeiwen.com/subject/weipndtx.html