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
相关产品推荐
相关产品推荐

