問(wèn)題描述:
現(xiàn)在的App里一共有2個(gè)UIWindow,一個(gè)是加載Tabbar的Window,一個(gè)是懸浮播放器的Window。 像Loading這種視圖默認(rèn)是加載在 UIApplication.shared.keyWindow上的。
但在升級(jí)到iOS14.x后出現(xiàn)了一個(gè)問(wèn)題。
當(dāng)點(diǎn)擊懸浮播放器的Window時(shí),App的KeyWindow會(huì)自動(dòng)變成懸浮播放器的Window。
這個(gè)時(shí)候再去添加Loading到KeyWindow上就會(huì)出現(xiàn)視圖錯(cuò)亂的Bug。
明明在iOS14以前都是好的,為什么在iOS14.5的系統(tǒng)就會(huì)出現(xiàn)這個(gè)問(wèn)題。
探究過(guò)程:
方案一:
首先想到的就是通過(guò)runtime Hook的方式去監(jiān)聽(tīng) UIApplication.shared.keyWindow的Set方法,來(lái)確定改變時(shí)機(jī)。
但KeyWindow屬性在UIKit中標(biāo)記為{ get }. 故方案不可取。?

方案二:
通過(guò)Symbolic BreakPoint [符號(hào)斷點(diǎn)]來(lái)查看其方法調(diào)用堆棧。

那就讓我們開(kāi)始吧~
首先通過(guò)斷點(diǎn)工具查看 UIApplication.shared.keyWindow的具體實(shí)現(xiàn)。

其實(shí)是調(diào)用了UIWindow 的 KeyWindow 方法。
然后 通過(guò)反編譯來(lái)獲取 UIWindow的KeyWindow方法具體實(shí)現(xiàn),發(fā)現(xiàn)無(wú)法獲取方法的具體實(shí)現(xiàn)。
這個(gè)時(shí)候就通過(guò)classdump獲取UIWindow類(lèi)的所有方法,逐個(gè)查閱, 找出可能有關(guān)系的方法。
再通過(guò)斷點(diǎn)找出對(duì)應(yīng)堆棧。
具體UIKit的class dump網(wǎng)上已經(jīng)有很多人做了,這里提供一個(gè)現(xiàn)成的UIWindow.h的頭文件內(nèi)容。
在全局搜索了所有關(guān)于Keywindow的方法后,

- (void)_makeKeyWindowIgnoringOldKeyWindow:(BOOL)arg1;
這個(gè)方法的可能性最強(qiáng)。
在Xcode中繼續(xù)打一個(gè)symbolic 斷點(diǎn)。用來(lái)監(jiān)聽(tīng)[UIWindow _makeKeyWindowIgnoringOldKeyWindow:]的調(diào)用。

在iOS14.5的手機(jī)上當(dāng)點(diǎn)擊新的窗口時(shí),會(huì)觸發(fā)斷點(diǎn)。
而在iOS13.6的系統(tǒng)上。斷點(diǎn)不會(huì)觸發(fā)。
這也證實(shí)了筆者的猜測(cè)。

那么是為什么在點(diǎn)擊新的Window上時(shí)會(huì)觸發(fā)Window改變呢?
通過(guò)查看方法調(diào)用順序,發(fā)現(xiàn)是[_UIRemoteKeyboards peekApplicationEvent:]觸發(fā)了KeyWindow的改變。

我們?cè)倏纯?code>[_UIRemoteKeyboards peekApplicationEvent:] 做了些什么, 原來(lái)在其方法內(nèi)部調(diào)用了更新KeyWindow的方法。

結(jié)論:
在iOS14.x后點(diǎn)擊新創(chuàng)建的Window KeyWindow會(huì)切換,主要是蘋(píng)果在iOS14.x后修改了UIKit的底層實(shí)現(xiàn)。 在iOS14.x版本后點(diǎn)擊Window觸發(fā)的[_UIRemoteKeyboards peekApplicationEvent:]會(huì)調(diào)用[UIWindow _makeKeyWindowIgnoringOldKeyWindow:]來(lái)更新KeyWindow.
而在iOS14.x之前也會(huì)觸發(fā)[_UIRemoteKeyboards peekApplicationEvent:]方法,但不會(huì)調(diào)用[UIWindow _makeKeyWindowIgnoringOldKeyWindow:]來(lái)更新KeyWindow。