分析一次比较奇怪的crash

客户反馈在使用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前缀的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容