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

Swift使用URLSession请求偶现NSURLErrorDomain -1005连接丢失错误如何解决

错误原因分析

NSURLErrorDomain Code=-1005 对应的官方错误类型是 NSURLErrorNetworkConnectionLost,核心触发逻辑是:客户端使用已经被服务端/中间代理节点主动关闭的HTTP Keep-Alive长连接发送请求,系统检测到连接已经失效就会抛出该错误。你在重试场景下高频触发该错误,是因为刷新Token后立刻复用了刚结束请求的旧连接,此时旧连接刚好被服务端回收,导致请求失败。

现有代码的问题
  • 无重试次数限制:retry() 方法是递归调用,如果持续返回触发刷新的状态码,会进入无限重试循环,短时间大量请求会触发服务端限流、主动断开连接,进一步放大-1005错误的出现概率。
  • Token刷新逻辑存在并发冲突:多个请求同时触发Token刷新时,会同时发起多个刷新请求,服务端通常刷新一次就会作废旧的Refresh Token,导致后续刷新请求全部失败,引发更多重试请求形成风暴。
  • 错误处理存在漏判:refreshAccessToken 方法中guard let data = data, err == nil else {return} 分支直接退出,没有调用回调函数,外层请求逻辑会一直挂起,请求堆积会引发连接资源耗尽、连接异常。
  • 解析逻辑存在崩溃风险:do-catch 块内使用 try! 解析JSON,解析失败会直接触发崩溃,完全绕过了catch逻辑。
  • performRetry变量无线程安全保障:如果该变量是全局/类属性,多线程并发读写时会出现状态错乱,导致重复重试、逻辑异常。
原生URLSession解决方案

1. 优化Token刷新逻辑,增加并发排队机制

同一时间仅允许一个刷新请求执行,其余刷新请求排队等待结果,避免重复发起刷新请求:

// 新增类属性控制刷新并发
private var isRefreshing = false
private var pendingRefreshCallbacks: [(Bool, Bool, String) -> Void] = []
private let refreshLock = NSLock()

func refreshAccessToken(completionHandler: @escaping (Bool, Bool, String) -> Void) {
    refreshLock.lock()
    // 已有刷新请求在执行,将当前回调加入排队队列
    if isRefreshing {
        pendingRefreshCallbacks.append(completionHandler)
        refreshLock.unlock()
        return
    }
    isRefreshing = true
    refreshLock.unlock()
    
    URLSession.shared.dataTask(with: tokenRegenerateRequest) { data, res, err in
        var isSuccess = false
        var isFailed = false
        var errCode = ""
        
        defer {
            // 刷新完成后执行所有排队回调
            refreshLock.lock()
            self.isRefreshing = false
            let callbacks = self.pendingRefreshCallbacks
            self.pendingRefreshCallbacks.removeAll()
            refreshLock.unlock()
            callbacks.forEach { $0(isSuccess, isFailed, errCode) }
        }
        
        print("ERROR: ", err)
        print("DATA: ", data)
        if let _ = err {
            print("IN HERE")
            isFailed = true
            errCode = "INTERNAL_SERVER_ERROR"
            completionHandler(isSuccess, isFailed, errCode)
            return
        }
        guard let data = data, let httpResponse = res as? HTTPURLResponse else {
            isFailed = true
            errCode = "INVALID_RESPONSE"
            completionHandler(isSuccess, isFailed, errCode)
            return
        }
        
        if httpResponse.statusCode == 200, !data.isEmpty {
            do {
                // 替换try!为正常try,避免崩溃
                let decodedResponse = try JSONDecoder().decode(RefreshTokenResponse.self, from: data)
                print("ACCESS TOKEN REGENERATE REQUEST: ", decodedResponse)
                if !decodedResponse.accessToken.isEmpty && !decodedResponse.refreshToken.isEmpty {
                    // 此处需要将新Token更新到全局请求头配置中
                    isSuccess = true
                    completionHandler(isSuccess, isFailed, errCode)
                } else {
                    print("HERE1")
                    isFailed = true
                    completionHandler(isSuccess, isFailed, errCode)
                }
            } catch {
                print("HERE2: \(error)")
                isFailed = true
                completionHandler(isSuccess, isFailed, errCode)
            }
        } else {
            print("HERE4", httpResponse.statusCode)
            isFailed = true
            errCode = "\(httpResponse.statusCode)"
            completionHandler(isSuccess, isFailed, errCode)
        }
    }.resume()
}

2. 优化重试逻辑,增加次数限制和延迟

避免无限重试,同时增加短延迟等待系统清理失效连接,避免复用旧连接:

// retry方法新增重试次数字段,默认从0开始计数
func retry(urlString: String, method: String, requestBody: [String: Any], completionHandler: @escaping (Any?, HTTPURLResponse?, String?) -> Void, retryCount: Int = 0) {
    // 最多重试3次,避免无限循环
    guard retryCount < 3 else {
        completionHandler(nil, nil, "MAX_RETRY_EXCEEDED")
        return
    }
    
    // 执行原请求逻辑
    sendOriginalRequest(urlString: urlString, method: method, requestBody: requestBody) { [weak self] data, response, error in
        guard let self = self else { return }
        if let responseDesc = response?.allHeaderFields["responseDesc"] as? String, responseDesc == "SOMECODE" {
            var performRetry = false
            self.refreshAccessToken() { isSuccess,isFailed, errCode in
                if isSuccess {
                    performRetry = true
                    // 增加200ms延迟后重试,等待系统清理失效连接
                    DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
                        self.retry(urlString: urlString, method: method, requestBody: requestBody, completionHandler: completionHandler, retryCount: retryCount + 1)
                    }
                } else {
                    if !performRetry {
                        if errCode.isEmpty {
                            completionHandler(nil,nil,"OHNO")
                        } else {
                            completionHandler(nil,nil,"ERROR")
                        }
                    }
                }
            }
        } else {
            completionHandler(data, response, error)
        }
    }
}

3. 可选优化:自定义URLSession配置

如果要进一步降低连接复用失效的概率,可以不用系统默认的URLSession.shared,自定义Session调整连接参数:

private lazy var customSession: URLSession = {
    let config = URLSessionConfiguration.default
    config.timeoutIntervalForRequest = 30
    // 调整单域名最大连接数,根据业务实际情况调整
    config.httpMaximumConnectionsPerHost = 10
    return URLSession(configuration: config)
}()
// 后续所有网络请求统一使用customSession发起即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 19:15:03