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

iOS WKURLSchemeHandler调用didReceiveResponse时EXC_BAD_ACCESS崩溃求助

WKURLSchemeHandler调用urlSchemeTask.didReceive(urlResponse)时偶发EXC_BAD_ACCESS崩溃问题

问题描述

我使用WKURLSchemeHandler处理内部请求(如首页、网页恢复等),但调用urlSchemeTask.didReceive(urlResponse)时偶尔会触发EXC_BAD_ACCESS崩溃。

崩溃调用栈

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00000001a7dd23a0 JavaScriptCore`WTF::StringImpl::hashSlowCase() const + 92
    frame #1: 0x00000001a7d8266c JavaScriptCore`WTF::HashTable<WTF::Packed<WTF::StringImpl*>, WTF::Packed<WTF::StringImpl*>, WTF::IdentityExtractor, WTF::DefaultHash<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>>::lookupForWriting(WTF::Packed<WTF::StringImpl*> const&) + 88
    frame #2: 0x00000001a7d82594 JavaScriptCore`WTF::HashTable<WTF::Packed<WTF::StringImpl*>, WTF::Packed<WTF::StringImpl*>, WTF::IdentityExtractor, WTF::DefaultHash<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>>::expand(WTF::Packed<WTF::StringImpl*>*) + 324
    frame #3: 0x00000001a7d835e8 JavaScriptCore`WTF::HashTableAddResult<WTF::HashTableIterator<WTF::Packed<WTF::StringImpl*>, WTF::Packed<WTF::StringImpl*>, WTF::IdentityExtractor, WTF::DefaultHash<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>>> WTF::HashTable<WTF::Packed<WTF::StringImpl*>, WTF::Packed<WTF::StringImpl*>, WTF::IdentityExtractor, WTF::DefaultHash<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>, WTF::HashTraits<WTF::Packed<WTF::StringImpl*>>>::addPassingHashCode<WTF::HashSetTranslatorAdapter<WTF::UCharBufferTranslator>, WTF::HashTranslatorCharBuffer<char16_t> const&, WTF::HashTranslatorCharBuffer<char16_t> const&>(WTF::HashTranslatorCharBuffer<char16_t> const&, WTF::HashTranslatorCharBuffer<char16_t> const&) + 692
    frame #4: 0x00000001a7d8034c JavaScriptCore`WTF::AtomStringImpl::add(char16_t const*, unsigned int) + 240
    frame #5: 0x00000001a7d83e3c JavaScriptCore`WTF::AtomStringImpl::add(__CFString const*) + 332
    frame #6: 0x00000001aa7b5010 WebCore`WebCore::ResourceResponse::platformLazyInit(WebCore::ResourceResponseBase::InitLevel) + 336
    frame #7: 0x00000001a9f020ec WebKit`void WebCore::ResourceResponseBase::encode<IPC::Encoder>(IPC::Encoder&) const + 124
    frame #8: 0x00000001aa250a58 WebKit`bool WebKit::AuxiliaryProcessProxy::send<Messages::WebPage::URLSchemeTaskDidReceiveResponse>(Messages::WebPage::URLSchemeTaskDidReceiveResponse&&, unsigned long long, WTF::OptionSet<IPC::SendOption>) + 268
    frame #9: 0x00000001aa22c380 WebKit`WebKit::WebURLSchemeTask::didReceiveResponse(WebCore::ResourceResponse const&) + 192
    frame #10: 0x00000001aa106554 WebKit`WTF::Detail::CallableWrapper<-[WKURLSchemeTaskImpl didReceiveResponse:]::$_1, WebKit::WebURLSchemeTask::ExceptionType>::call() + 52
    frame #11: 0x00000001aa0fa7e4 WebKit`-[WKURLSchemeTaskImpl didReceiveResponse:] + 100
  * frame #12: 0x0000000104dab348 Browser`InternalSchemeHandler.webView(webView=0x000000010c853600, urlSchemeTask=__C.WKURLSchemeTask @ 0x000000016dd7c818, self=0x00000002808efe70) at InternalSchemeHandler.swift:98:27
    frame #13: 0x0000000104dabc64 Browser`@objc InternalSchemeHandler.webView(_:start:) at <compiler-generated>:0
    frame #14: 0x00000001aa1b8648 WebKit`WebKit::WebURLSchemeHandlerCocoa::platformStartTask(WebKit::WebPageProxy&, WebKit::WebURLSchemeTask&) + 524
    frame #15: 0x00000001aa20a0dc WebKit`WebKit::WebURLSchemeHandler::startTask(WebKit::WebPageProxy&, WebKit::WebProcessProxy&, WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::URLSchemeTaskParameters&&, WTF::CompletionHandler<void (WebCore::ResourceResponse const&, WebCore::ResourceError const&, WTF::Vector<char, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&) + 968
    frame #16: 0x00000001aa209cd0 WebKit`WebKit::WebPageProxy::startURLSchemeTaskShared(WTF::Ref<WebKit::WebProcessProxy, WTF::DumbPtrTraits<WebKit::WebProcessProxy>>&&, WTF::ObjectIdentifier<WebCore::PageIdentifierType>, WebKit::URLSchemeTaskParameters&&) + 108
    frame #17: 0x00000001aa209c24 WebKit`WebKit::WebPageProxy::startURLSchemeTask(WebKit::URLSchemeTaskParameters&&) + 52
    frame #18: 0x00000001aa4d44a8 WebKit`WebKit::WebPageProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder&) + 84824
    frame #19: 0x00000001a9ef885c WebKit`IPC::MessageReceiverMap::dispatchMessage(IPC::Connection&, IPC::Decoder&) + 124
    frame #20: 0x00000001aa2272f0 WebKit`WebKit::WebProcessProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder&) + 40
    frame #21: 0x00000001a9edbdec WebKit`IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder>>) + 656
    frame #22: 0x00000001a9edb6f4 WebKit`IPC::Connection::dispatchIncomingMessages() + 460
    frame #23: 0x00000001a7dbe3c8 JavaScriptCore`WTF::RunLoop::performWork() + 484
    frame #24: 0x00000001a7dbf0b0 JavaScriptCore`WTF::RunLoop::performWork(void*) + 36
    frame #25: 0x000000019db7076c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
    frame #26: 0x000000019db70668 CoreFoundation`__CFRunLoopDoSource0 + 208
    frame #27: 0x000000019db6f960 CoreFoundation`__CFRunLoopDoSources0 + 268
    frame #28: 0x000000019db69a8c CoreFoundation`__CFRunLoopRun + 824
    frame #29: 0x000000019db6921c CoreFoundation`CFRunLoopRunSpecific + 600
    frame #30: 0x00000001b4be8784 GraphicsServices`GSEventRunModal + 164
    frame #31: 0x00000001a05a2200 UIKitCore`-[UIApplication _run] + 1072
    frame #32: 0x00000001a05a7a74 UIKitCore`UIApplicationMain + 168
    frame #33: 0x000000010221d568 TDMBrowser`main at AppDelegate.swift:32:7
    frame #34: 0x000000019d8296c0 libdyld.dylib`start + 4

我的URLSchemeHandler实现代码

func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
    if taskDic[ObjectIdentifier(urlSchemeTask)] != false {
        taskDic[ObjectIdentifier(urlSchemeTask)] = true
    }

    guard let url = urlSchemeTask.request.url else {
        if taskDic[ObjectIdentifier(urlSchemeTask)] == true {
            urlSchemeTask.didFailWithError(InternalPageSchemeHandlerError.badURL)

            taskDic.removeValue(forKey: ObjectIdentifier(urlSchemeTask))
        }

        return
    }

    let path = url.path.starts(with: "/") ? String(url.path.dropFirst()) : url.path
    guard let responder = InternalSchemeHandler.responders[path] else {
        if taskDic[ObjectIdentifier(urlSchemeTask)] == true {
            urlSchemeTask.didFailWithError(InternalPageSchemeHandlerError.noResponder)

            taskDic.removeValue(forKey: ObjectIdentifier(urlSchemeTask))
        }

        return
    }
    guard let (urlResponse, data) = responder.response(forRequest: urlSchemeTask.request) else {
        if taskDic[ObjectIdentifier(urlSchemeTask)] == true {
            urlSchemeTask.didFailWithError(InternalPageSchemeHandlerError.responderUnableToHandle)

            taskDic.removeValue(forKey: ObjectIdentifier(urlSchemeTask))
        }

        return
    }

    if taskDic[ObjectIdentifier(urlSchemeTask)] == true {
        urlSchemeTask.didReceive(urlResponse)
        urlSchemeTask.didReceive(data)
        urlSchemeTask.didFinish()

        taskDic.removeValue(forKey: ObjectIdentifier(urlSchemeTask))
    }
}

func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
    moduleLogger.debug("stop urlSchemeTask: \(urlSchemeTask)")
    taskDic[ObjectIdentifier(urlSchemeTask)] = false
}

解决建议

根据调用栈分析,崩溃发生在WebCore处理ResourceResponse的platformLazyInit阶段,涉及StringImpl的哈希计算,大概率是ResourceResponse内部的某个字符串属性为空或已被释放,或多线程访问导致的野指针问题。

  • 检查responder.response(forRequest:)返回的urlResponse初始化是否完整:确保URL、MIME类型等必填参数不为空,HTTP头中避免传入nil或空字符串。比如创建URLResponse时,必须传入有效的URL对象,MIME类型如text/html不能留空。
  • 强制主线程执行task回调:即使当前调用在主线程,若responder.response(forRequest:)在后台线程生成urlResponse,可能存在线程安全问题。所有urlSchemeTask的didReceive、didFinish等方法调用,都要切换到主线程执行:
    DispatchQueue.main.async {
        urlSchemeTask.didReceive(urlResponse)
        urlSchemeTask.didReceive(data)
        urlSchemeTask.didFinish()
    }
    
  • 简化taskDic的管理逻辑:原逻辑存在初始化不严谨的问题,可改用defer统一清理,避免重复判断:
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        let taskID = ObjectIdentifier(urlSchemeTask)
        taskDic[taskID] = true
        
        defer {
            taskDic.removeValue(forKey: taskID)
        }
    
        guard taskDic[taskID] == true, let url = urlSchemeTask.request.url else {
            urlSchemeTask.didFailWithError(InternalPageSchemeHandlerError.badURL)
            return
        }
    
        // 后续逻辑中,每次执行回调前都先判断任务是否已取消
        guard taskDic[taskID] == true else { return }
        // ... 其余代码
    }
    
  • 确保task回调只调用一次:避免因任务取消后仍执行didReceive等方法,导致WebKit内部状态混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 08:52:33