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

NSURLSession的waitsForConnectivity标识在OperationQueue运行DataTask时被忽略

URLSession的waitsForConnectivity配置在自定义Operation中失效

问题描述

我自定义了一个实现Operation协议的类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上运行,Operation的相关方法实现如下:

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

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

MyURLRequest同时实现了URLSessionDataDelegate和URLSessionTaskDelegate协议,作为自身创建的URLSession的代理。

当前问题:waitsForConnectivity配置失效,构造方法中将其设为true后运行时被忽略。关闭网络场景下,请求会立即结束返回-1009错误,urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)回调立即触发,func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask)完全不会被调用。
已确认waitsForConnectivity配置正确:在didCompleteWithError回调中查看task对应的配置,该值确实为true。
不通过OperationQueue发起请求时功能完全符合预期,推测问题和OperationQueue的使用有关。

问题更新1

初步判断问题根源是Operation在请求完成前被提前释放,尝试用DispatchGroup做同步,代码如下,没有生效,请求依旧不会等待网络连通:

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

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

    localURLSession.finishTasksAndInvalidate()
}

其中leave()方法在URLSessionDelegate的回调中调用。

问题更新2

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=}

根因分析

  1. 使用了默认同步Operation:默认Operation是同步执行的,start方法执行完成后会被自动标记为isFinished = true,随即被OperationQueue释放,URLSession的delegate也跟着被销毁,还在等待网络的任务失去代理后直接返回无网络错误。
  2. 方法调用时机错误:在start方法刚启动任务就调用了localURLSession.finishTasksAndInvalidate(),虽然该方法会等待现有任务完成,但session代理被提前释放后,等待逻辑会直接终止。
  3. DispatchGroup用法导致死锁:如果URLSession的代理回调队列和当前被wait()阻塞的队列是同一个,回调永远不会执行,leave()永远不会被调用;即使队列不同,阻塞Operation执行线程的做法也不符合异步Operation的设计规范。

解决方案

1. 实现异步Operation

自定义Operation要支持异步执行,必须重写状态属性,手动触发KVO通知:

private var _isExecuting: Bool = false
override open var isExecuting: Bool {
    get { _isExecuting }
    set {
        willChangeValue(forKey: "isExecuting")
        _isExecuting = newValue
        didChangeValue(forKey: "isExecuting")
    }
}

private var _isFinished: Bool = false
override open var isFinished: Bool {
    get { _isFinished }
    set {
        willChangeValue(forKey: "isFinished")
        _isFinished = newValue
        didChangeValue(forKey: "isFinished")
    }
}

override open var isAsynchronous: Bool {
    return true
}

2. 调整start方法逻辑

移除start中的finishTasksAndInvalidate调用,仅做任务启动和状态更新:

override open func start() {
    if isCancelled {
        isFinished = true
        return
    }
    // 标记Operation开始执行
    isExecuting = true
    startDate = Date()
    sessionTask?.resume()
}

3. 请求结束后更新状态并销毁session

在urlSession(_:task:didCompleteWithError:)回调中统一处理收尾逻辑:

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    // 先处理业务逻辑
    defer {
        // 销毁session
        localURLSession.finishTasksAndInvalidate()
        // 标记Operation执行结束
        isExecuting = false
        isFinished = true
    }
    // 其余错误处理逻辑
}

4. 调整cancel方法逻辑

取消任务时同步更新Operation状态:

override open func cancel() {
    super.cancel()
    sessionTask?.cancel()
    localURLSession.invalidateAndCancel()
    isExecuting = false
    isFinished = true
}

修改完成后在无网络场景下测试,taskIsWaitingForConnectivity回调会正常触发,网络恢复后请求会自动执行,不会提前返回-1009错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 01:15:03