客戶反饋在使用aspects hook UIView的setBackgroundColor:方法后,打開在線pdf的webview后打開我們的webview會crash。webview內(nèi)有對webview.scrollView添加backgroundColor的監(jiān)聽(addObserver)。
報錯信息如下:
2024-01-03 14:37:42.486091+0800 test4[9534:929623] -[WKScrollView _original_setBackgroundColor:]: unrecognized selector sent to instance 0x12c09f800
添加條件斷點,查看調(diào)用堆棧。
-[NSObject(NSObject) doesNotRecognizeSelector:]

比較奇怪,
1._original_setBackgroundColor:,方法哪里來的?
2.調(diào)用棧并不是一個正常的KVO調(diào)用棧。
3.為什么先打開pdf就會閃退,不打開則正常?
整理下思路:
aspects hook了hook UIView的setBackgroundColor:方法。也就是UIView類對象的setBackgroundColor: 方法交換了_objc_msgForward,在消息轉(zhuǎn)發(fā)第三步調(diào)用了forwardInvocation,并調(diào)用了ASPECTS_ARE_BEING_CALLED函數(shù)。
unrecognized selector方法是在消息轉(zhuǎn)發(fā)三步驟都沒找到,doesNotRecognizeSelector:內(nèi)拋出的crash。
所以在ASPECTS_ARE_BEING_CALLED函數(shù)內(nèi)加上斷點,試圖捕獲crash信息。但是測試發(fā)現(xiàn),webview.scrollView.backgroundColor = xxx,只有最后那次crash時未進來,其它每次都正常進入。
問題:doesNotRecognizeSelector是從哪里拋出?
思考:
添加KVO后,NSKVONotifying_WKScrollView重寫了setBackgroundColor:方法,內(nèi)部調(diào)用了super setBackgroundColor:。
webview.scrollView.backgroundColor = xxx執(zhí)行流程依次是:
1.NSKVONotifying_WKScrollView setBackgroundColor
2.UIView setBackgroundColor
3._objc_msgForward
4.ASPECTS_ARE_BEING_CALLED
但事實是沒進入ASPECTS_ARE_BEING_CALLED函數(shù)。
輸出下NSKVONotifying_WKScrollView的所有方法名,發(fā)現(xiàn)了一個奇怪的方法,_original_setContentInset:,前綴和_original_setBackgroundColor。
查找資料,找到這篇文章:
https://mp.weixin.qq.com/s/bHdtetOb3LQGRfRO9AFVQA
大概意思就是KVO的底層機制也分場景,像contentInset這種是另一種機制,會直接調(diào)用_CF_forwarding_prep_0進入消息轉(zhuǎn)發(fā)。并且會添加一個original前綴的方法到NSKVONotifying_XX類中,指向原始的IMP。
_CF_forwarding_prep_0在前文的調(diào)用棧截圖也看到了。
然后我測試加載pdf后會有_original_setContentInset,不加載則沒有。
猜測加載pdf內(nèi)部添加了KVO給contentInset。
進一步驗證,不打開pdf,但后續(xù)打開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。
驗證了結(jié)論。
backgroundColor在KVO時不會添加_original_setbackgroundColor方法,但是卻因為aspects的影響,導(dǎo)致backgroundColor在NSKVONotifying_WKScrollView在重寫時生成了類似contentInset的實現(xiàn):
原本應(yīng)當(dāng)是:
- (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最終在消息轉(zhuǎn)發(fā)步驟3,調(diào)用了帶上了original前綴的方法。