当 DataTask 在 OperationQueue 上 运行 时,NSURLSession .waitsForConnectivity 标志被忽略

NSURLSession .waitsForConnectivity flag ignored when DataTask being run on OperationQueue

我有一个手写的 class MyURLRequest,它实现了操作。在里面创建 URLSession,配置它

public init(shouldWaitForConnectivity: Bool, timeoutForResource: Double?) {
    baseUrl = URL(string: Self.relevantServerUrl + "api/")
    self.shouldWaitForConnectivity = shouldWaitForConnectivity
    self.timeoutForResource = timeoutForResource
    super.init()
    localURLSession = URLSession(configuration: localConfig, delegate: self, delegateQueue: nil)

}

public var localConfig: URLSessionConfiguration {
    let res = URLSessionConfiguration.default
    res.allowsCellularAccess = true
    if let shouldWaitForConnectivity = shouldWaitForConnectivity {
        res.waitsForConnectivity = shouldWaitForConnectivity
        if let timeoutForResource = timeoutForResource {
            res.timeoutIntervalForResource = timeoutForResource
        }
    }
    return res
}

创建 URLRequest、dataTask,然后在 OperationQueue 上 运行。操作方法如下所示

override open func start() {
    if isCancelled {
        isFinished = true
        return
    }
    
    startDate = Date()
    sessionTask?.resume()
    localURLSession.finishTasksAndInvalidate()
}

override open func cancel() {
    super.cancel()
    sessionTask?.cancel()
}

MyURLRequest 还实现了 URLSessionDataDelegateURLSessionTaskDelegate 以及它自己的 URLSession 的代理。

waitsForConnectivity NSURLSessionConfiguration 的标志有问题。在构造函数中我将它设置为 true,但是这个标志被忽略了。在 运行 时间,当网络关闭时,请求立即完成并出现错误 -1009。 URLSessionTaskDelegate 的方法 urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 立即被触发。 func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) 根本没有被调用。

绝对不是的原因是标志 waitsForConnectivity 没有正确设置:我已经检查了 urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) ,waitsForConnectivity == true。

我也尝试在没有操作队列的情况下发出请求,结果很顺利 - 表现如预期。也许与 OperationQueue 有关。非常感谢您的帮助!

更新: 问题的根源似乎是 Operation 发布得太早(当请求尚未完成时)。我尝试使用 DispatchGroup():

来同步它们
override open func start() {
    if isCancelled {
        isFinished = true
        return
    }
    startDate = Date()

    dispatchGroup.enter()
    sessionTask?.resume()
    dispatchGroup.wait()

    localURLSession.finishTasksAndInvalidate()
}

在 URLSessionDelegate 的方法中调用了 .leave()。什么都没有改变,仍然没有等待连接。

更新: 这是我在 didCompleteWithError:

中遇到的错误
Error Domain=NSURLErrorDomain Code=-1009 "" UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x7fc319112de0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <6388AD46-8497-40DF-8768-44FEBB84A8EC>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <6388AD46-8497-40DF-8768-44FEBB84A8EC>.<1>",
    "LocalDataTask <26BCBD73-FC8B-4A48-8EA2-1172ABB8093C>.<1>"
), NSLocalizedDescription=., NSErrorFailingURLStringKey=}

我认为问题的根源在于您对 finishTasksAndInvalidate 的使用。看起来您依赖于同步等待所有未决任务完成的方法,但根据文档,它不是这样工作的。

这是对我认为正在发生的事情的更深入的解释。默认情况下,Operationstart 方法 returns 一样被认为是完整的。这绝对不是您需要的,因为任务必须异步完成。 Operation 能够立即支持这种行为。

您的 start returns 立即,远在会话有任何时间完成您已启动的任务之前。然后,随着操作完成,队列将其删除。这通常最终成为该实例的唯一所有者。如果是这样,它将启动操作 deinit 进程,最终释放 URLSession。在 那个 点,会话看起来可能会做一些清理并终止任何未完成的任务,将一些调用转发给它的委托。我不确定 URLSession 是否会执行此操作,但根据您所看到的情况,听起来可能会执行此操作。

为了实现你想要的,我认为你需要重构你的 NSOperation 子类,使其完全异步,并且仅在启动的任务完成时才完成。

构建一个完全线程安全的异步 NSOperation 子类真的很痛苦。如果这不是您之前处理过的问题,您可以在此处查看实施:https://github.com/ChimeHQ/OperationPlus