分析一次比較奇怪的crash

客戶反饋在使用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:]


截屏2024-01-03 14.39.47.png

比較奇怪,
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前綴的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容