You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS18下WKWebView调用evaluateJavaScript多线程崩溃求助

解决iOS18下多WKWebView调用evaluateJavaScript导致递归锁崩溃问题

核心问题分析

iOS18对WKWebView的内部锁机制做了更严格的校验,当多个WKWebView实例同时在didFinish回调中调用evaluateJavaScript时,会触发os_unfair_lock的递归锁检测,导致崩溃。iOS17及更早版本未启用该严格校验,因此无此问题。

可行解决方案

1. 限制evaluateJavaScript的并发执行数量

通过信号量控制同时执行的JS评估任务数,匹配你测试的正常并发阈值(建议3个以内),避免触发iOS18的内部锁冲突:

private let jsEvalSemaphore = DispatchSemaphore(value: 3)

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    guard webView.url != nil else { return }
    
    DispatchQueue.main.async { [weak self] in
        self?.jsEvalSemaphore.wait()
        webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { [weak self] html, error in
            defer { self?.jsEvalSemaphore.signal() }
            guard let htmlString = html as? String else { return }
            // 将Kanna解析逻辑移至后台队列,避免阻塞主线程
            DispatchQueue.global(qos: .background).async {
                if let doc = try? HTML(html: htmlString, encoding: .utf8) {
                    // 处理解析结果
                }
            }
        })
    }
}

2. 延迟调用evaluateJavaScript

在didFinish触发后延迟一小段时间再执行JS评估,给WKWebView足够时间完成内部状态清理,避开锁冲突窗口:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    guard let url = webView.url else { return }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak webView] in
        guard let webView = webView else { return }
        webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { html, error in
            guard let htmlString = html as? String else { return }
            // 后台执行解析逻辑
            DispatchQueue.global().async {
                if let doc = try? HTML(html: htmlString, encoding: .utf8) {
                    // 处理解析结果
                }
            }
        })
    }
}

3. 复用WKWebView实例而非创建多个

同时初始化10个WKWebView属于高资源消耗场景,iOS18对这类场景的管控更严格。改用WebView复用池,依次加载URL并处理解析:

private let webView = WKWebView()
private var pendingURLs: [URL] = []
private var isProcessing = false

func startProcessingURLs(_ urls: [URL]) {
    pendingURLs.append(contentsOf: urls)
    processNextURL()
}

private func processNextURL() {
    guard !isProcessing, let nextURL = pendingURLs.first else { return }
    isProcessing = true
    webView.load(URLRequest(url: nextURL))
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { [weak self] html, error in
        guard let self = self, let htmlString = html as? String else { return }
        // 后台执行解析
        DispatchQueue.global().async {
            if let doc = try? HTML(html: htmlString, encoding: .utf8) {
                // 处理解析结果
            }
            // 解析完成后处理下一个URL
            DispatchQueue.main.async {
                self.pendingURLs.removeFirst()
                self.isProcessing = false
                self.processNextURL()
            }
        }
    })
}

4. 确保解析逻辑不在主线程阻塞

Kanna的HTML解析属于CPU密集型任务,若在主线程执行会阻塞主线程,间接引发WebView内部锁冲突。务必将解析逻辑移至后台队列:

webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { html, error in
    guard let htmlString = html as? String else { return }
    // 后台队列执行解析
    DispatchQueue.global(qos: .userInitiated).async {
        if let doc = try? HTML(html: htmlString, encoding: .utf8) {
            let titles = doc.xpath("//h1/text()").compactMap { $0.text }
            // 若需更新UI,切回主线程
            DispatchQueue.main.async {
                // 更新UI逻辑
            }
        }
    }
})

内容的提问来源于stack exchange,提问作者Zhang Yong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:35:23