WKWebView 導(dǎo)航歷史(backForwardList)行為解析與控制方案

一、背景

在 iOS 開發(fā)中,WKWebView 是承載 H5 的核心組件。
在實(shí)際業(yè)務(wù)中,經(jīng)常會(huì)遇到以下問題:

  • 每次調(diào)用 webView.load(request) 后,backForwardList.backList 都會(huì)不斷增長
  • 無法像 UIWebView 或?yàn)g覽器那樣手動(dòng)清空歷史
  • 登錄 / 切賬號(hào) / 切環(huán)境時(shí),希望 Web 頁面“像第一次打開一樣”
  • Web 返回邏輯與 Native 返回邏輯產(chǎn)生沖突

因此,有必要從原理層面理解 WKWebView 的導(dǎo)航棧機(jī)制,并給出可落地的工程解決方案。


二、核心結(jié)論

WKWebView 不提供任何公開 API 來直接清空 backForwardList
backForwardList 是否增長,取決于 WebKit 是否判定為一次 new navigation,而不是你是否調(diào)用了 load()。


三、原理解析

1. backForwardList 是什么?

  • WKBackForwardList 是 WebKit 內(nèi)部維護(hù)的導(dǎo)航歷史棧

  • 包含三部分:

    • backList:可回退頁面
    • currentItem:當(dāng)前頁面
    • forwardList:可前進(jìn)頁面
  • 只讀、不可修改

webView.backForwardList.backList      // 只讀
webView.backForwardList.currentItem
webView.backForwardList.forwardList

2. 是否壓棧的根本判斷標(biāo)準(zhǔn)

是否被 WebKit 認(rèn)為是一次 “new navigation”

與以下因素有關(guān):

  • URL 是否變化
  • 是否是 same-document navigation
  • 是否是 reload
  • 是否是 replace 行為
  • 是否涉及 POST / header / redirect

? 與 load() 調(diào)用次數(shù)不等價(jià)


四、行為分類與壓棧規(guī)則

1?? 一定會(huì)壓棧的情況

行為 說明
load(newURL) 標(biāo)準(zhǔn)新導(dǎo)航
POST 請求 即使 URL 相同
Header / Cookie 變化 WebKit 視為新請求
302 / 307 重定向 源 URL 會(huì)進(jìn)棧
location.href = JS 新導(dǎo)航

2?? 不會(huì)壓棧的情況

行為 說明
reload() 刷新當(dāng)前 entry
location.replace() 替換當(dāng)前 entry
history.replaceState() Web 內(nèi)替換
hash 變化 same-document
SPA 內(nèi)部路由 不進(jìn)入 native 棧

五、關(guān)鍵代碼示例

1. 觀察 backForwardList 變化(調(diào)試必備)

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("backList:",
          webView.backForwardList.backList.map { $0.url.absoluteString })
    print("current:",
          webView.url?.absoluteString ?? "")
}

2. “等效清空歷史”的方案一:loadHTMLString

let html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
    window.location.replace('\(url.absoluteString)');
</script>
</head>
<body></body>
</html>
"""

webView.loadHTMLString(html, baseURL: nil)

效果:

  • 重置導(dǎo)航起點(diǎn)
  • backList 為空
  • 返回直接交給 Native

3. “徹底清空”的方案二:重建 WKWebView(最穩(wěn))

func recreateWebView(with request: URLRequest) {
    webView.removeFromSuperview()
    webView.navigationDelegate = nil
    webView.uiDelegate = nil

    let config = WKWebViewConfiguration()
    let newWebView = WKWebView(frame: container.bounds,
                              configuration: config)
    newWebView.navigationDelegate = self
    newWebView.uiDelegate = self

    container.addSubview(newWebView)
    webView = newWebView
    webView.load(request)
}

六、時(shí)序圖(Navigation & backForwardList)

Native(WKWebView)            WebKit
     │                         │
     │ load(A)                 │
     ├────────────────────────?│
     │                         │ create entry A
     │                         │ backList = []
     │?────────────────────────┤
     │
     │ load(B)                 │
     ├────────────────────────?│
     │                         │ new navigation
     │                         │ backList = [A]
     │?────────────────────────┤
     │
     │ reload()                │
     ├────────────────────────?│
     │                         │ no new entry
     │                         │ backList = [A]
     │?────────────────────────┤
     │
     │ location.replace(C)     │
     ├────────────────────────?│
     │                         │ replace current
     │                         │ backList = [A]
     │?────────────────────────┤

七、常見誤區(qū)

誤區(qū) 說明
以為 load() 次數(shù) = 歷史條數(shù) ?
用 JS 清 native 棧 ? 不可能
stopLoading() 能清歷史 ?
allowsBackForwardNavigationGestures 能影響棧 ? 只影響手勢

八、工程級(jí)建議

推薦決策樹

  • 希望“像第一次打開”
    loadHTMLString 作為新起點(diǎn)
  • 賬號(hào) / 登錄態(tài) / 環(huán)境切換
    → 重建 WKWebView
  • Web 內(nèi)部返回邏輯
    → SPA + replaceState

九、總結(jié)

  1. WKWebView.backForwardList 是 WebKit 內(nèi)部導(dǎo)航棧,不可直接清空

  2. 是否壓棧取決于 new navigation 判定

  3. load(request) ≠ 一定壓棧,但大多數(shù)業(yè)務(wù)場景會(huì)

  4. 工程上只有兩條“正道”:

    • 重置起點(diǎn)(loadHTMLString)
    • 重建實(shí)例(recreate WKWebView)
  5. 所有“試圖直接操作 backList 的方案”都是不可行的

正確的姿勢不是“怎么清?!?,而是“是否應(yīng)該有?!薄?/strong>


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

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

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