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

WKURLSchemeHandler任务取消引发NSInternalInconsistencyException崩溃求助

解决WKURLSchemeHandler任务取消时的崩溃问题

在使用WKURLSchemeHandler处理任务取消时,调用webView(_:stop:)并打印“Stopping task ...”后,偶尔会出现try Task.checkCancellation()未抛出异常,或后续代码仍执行的情况,导致调用urlSchemeTask.didReceive、didFinish或didFailWithError时触发崩溃:

Stopping task <WKURLSchemeTaskImpl: 0x7fd445c209c0>
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This task has already been stopped'

示例实现

import WebKit

class AsyncURLSchemeHandler: NSObject, WKURLSchemeHandler {
    private var pendingTasks = [ObjectIdentifier: TaskItem]()
    
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        guard let task = pendingTasks.removeValue(forKey: urlSchemeTask.id) else { return }
        print("Stopping task \(urlSchemeTask)")
        task.stop()
    }

    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        let task = Task { [weak self] in
            var request = urlSchemeTask.request
        
            // Do some mutation on the request
            
            do {
                try Task.checkCancellation()
                
                 // Conditionally get a URLSession 
                let session: URLSession 
                
                // Fire off the request
                let (data, response) = try await session.data(for: request)
                
                await Task.yield()
                try Task.checkCancellation()

                // Report back to the scheme task
                // Either of these !! may crash in this implementation
                urlSchemeTask.didReceive(response) // !!
                urlSchemeTask.didReceive(data) // !!
                urlSchemeTask.didFinish() // !!
                
            } catch is CancellationError {
                // Do not call didFailWithError, didFinish, or didReceive in this case
                print("Task for WKURLSchemeTask \(urlSchemeTask) has been cancelled")
            } catch {
                if !Task.isCancelled {
                    // !! This can crash, too
                    urlSchemeTask.didFailWithError(error)
                }
            }
            
            self?.pendingTasks.removeValue(forKey: urlSchemeTask.id)
        }
        
        pendingTasks[urlSchemeTask.id] = .init(urlSchemeTask: urlSchemeTask, task: task)
    }
}

private extension WKURLSchemeTask {
    var id: ObjectIdentifier {
        ObjectIdentifier(self)
    }
}

private struct TaskItem {
    enum Error: Swift.Error {
        case manualCancellation
    }
    
    let urlSchemeTask: WKURLSchemeTask
    let task: Task<Void, Never>
    
    /// Should be called when urlSchemeTask has been stopped by the system
    /// Calling anything on the urlSchemeTask afterwards would result in an exception
    func stop() {
        task.cancel()
    }
    
    /// Should be called when the urlSchemeTask should be stopped manually
    func cancel() {
        task.cancel()
        urlSchemeTask.didFailWithError(Error.manualCancellation)
    }
}

解决方案

1. 增加任务状态的原子性跟踪

Task.cancel()是异步操作,无法保证Task.checkCancellation()立即生效。给TaskItem添加原子性的状态标记,在调用urlSchemeTask的方法前先检查状态:

private struct TaskItem {
    enum Error: Swift.Error {
        case manualCancellation
    }
    
    let urlSchemeTask: WKURLSchemeTask
    let task: Task<Void, Never>
    private(set) var isStopped = false
    private let queue = DispatchQueue(label: "com.example.taskitem.state")
    
    func stop() {
        queue.sync {
            isStopped = true
            task.cancel()
        }
    }
    
    func isTaskActive() -> Bool {
        queue.sync { !isStopped }
    }
}

2. 同步控制任务操作与状态检查

在异步任务的关键节点,除了调用Task.checkCancellation(),还需检查TaskItem的状态,避免在任务已停止后调用urlSchemeTask的方法:

func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
    let taskItem = TaskItem(urlSchemeTask: urlSchemeTask, task: Task { [weak self, weak taskItem] in
        guard let taskItem = taskItem else { return }
        var request = urlSchemeTask.request
    
        // Do some mutation on the request
        
        do {
            try Task.checkCancellation()
            guard taskItem.isTaskActive() else { throw CancellationError() }
            
            // Conditionally get a URLSession 
            let session: URLSession = URLSession.shared
            
            // Fire off the request
            let (data, response) = try await session.data(for: request)
            
            try Task.checkCancellation()
            guard taskItem.isTaskActive() else { throw CancellationError() }

            // Report back to the scheme task
            if taskItem.isTaskActive() {
                urlSchemeTask.didReceive(response)
                urlSchemeTask.didReceive(data)
                urlSchemeTask.didFinish()
            }
            
        } catch is CancellationError {
            print("Task for WKURLSchemeTask \(urlSchemeTask) has been cancelled")
        } catch {
            if taskItem.isTaskActive() && !Task.isCancelled {
                urlSchemeTask.didFailWithError(error)
            }
        }
        
        self?.pendingTasks.removeValue(forKey: urlSchemeTask.id)
    })
    
    pendingTasks[urlSchemeTask.id] = taskItem
}

3. 确保任务状态的线程安全

所有涉及TaskItem状态修改和读取的操作,都通过串行队列同步执行,避免并发导致的状态不一致问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 17:15:53