首页 > 解决方案 > 在 OperationQueue 上运行 DataTask 时忽略 NSURLSession .waitsForConnectivity 标志

问题描述

我有一个手写类 MyURLRequest,它实现了 Operation。在它里面创建 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 还实现了它自己URLSessionDataDelegateURLSessionTaskDelegateURLSession 的委托。

waitsForConnectivityNSURLSessionConfiguration 的标志有问题。在构造函数中,我将其设置为 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 有关。感谢您的帮助!

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

override open func start() {
    if isCancelled {
        isFinished = true
        return
    }
    startDate = Date()

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

    localURLSession.finishTasksAndInvalidate()
}

其中 .leave() 在 URLSessionDelegate 的方法中被调用。没有任何改变,仍然没有等待连接。

更新: 这是我在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=}

标签: iosswiftmultithreadingnsurlsessionoperation

解决方案


我相信问题的根源在于您使用finishTasksAndInvalidate. 看起来您正在依赖该方法同步等待所有待处理的任务完成,但根据文档,它不是这样工作的。

这是对我认为正在发生的事情的更深入的解释。默认情况下Operation,一旦start方法返回,an 就被认为是完整的。这绝对不是您需要的,因为任务必须异步完成。无法开箱即用地支持这种行为Operation

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

为了实现你想要的,我认为你需要将你的NSOperation子类重组为完全异步的,并且只在启动任务完成时完成。

构建一个完全线程安全的异步NSOperation子类是一件非常痛苦的事情。如果这不是您之前解决过的问题,您可以在此处查看实现:https ://github.com/ChimeHQ/OperationPlus


推荐阅读