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










网友评论