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

Alamofire会话失效问题求助:单例网络请求重复调用崩溃

单例模式下Alamofire Session失效问题求助

我在网络请求中使用单例模式,却遇到了Session失效的问题,错误提示如下:

Session was invalidated without error, so it was likely deinitialized unexpectedly. Be sure to retain a reference to your Session for the duration of your requests.

我的网络封装代码:

public static let shared = NetworkingClient()
private var requests = [ApiRequest]()
private var completedRequests = 0
private var session: Session!
func InvalidateSession () {
    if #available(iOS 9.0, *) {
        Alamofire.Session.default.session.getAllTasks { (tasks) in
                tasks.forEach{ $0.cancel() }
            }
        } else {
        Alamofire.Session.default.session.getTasksWithCompletionHandler { (sessionDataTask, uploadData, downloadData) in
            sessionDataTask.forEach { $0.cancel() }
            uploadData.forEach { $0.cancel() }
            downloadData.forEach { $0.cancel() }
        }
    }
}

API调用代码(场景:增加quantity时Alamofire崩溃,希望避免重复请求,仅调用一次API):

func gasEstimation(chain_Id: Int, contractAddress: String, fromAddress: String, toAddress: String, tokenId: String, contractType: String, quantity: String) {
    let apiRequest = [ApiRequest(type: .get, path: "gasEstimation?chainId=\(chain_Id)&contractAddress=\(contractAddress)&from=\(fromAddress.trimmingCharacters(in: .whitespacesAndNewlines))&to=\(toAddress.trimmingCharacters(in: .whitespacesAndNewlines))&type=\(contractType)&quantity=\(quantity)&tokenId=\(tokenId)")]
    NetworkingClient.shared.performRequest(requests: apiRequest) { path, data, error in
        if data != nil {
            let json =  JSON(data!)
            let gasTokenModel = NftTransferModel(json: json)
            self.presenter?.onReceiveFee(estimationFee: gasTokenModel,error: nil)
        }else{
            let error = JSON(error!)
            self.presenter?.onReceiveFee(estimationFee: nil,error: error["message"].stringValue)
        }
    }
}

问题分析

错误提示明确指出核心问题:Session被意外释放,必须在请求全程持有Session的引用。你的代码存在以下几个关键问题:

  • 单例NetworkingClient中声明了session属性但从未初始化,反而直接依赖Alamofire.Session.default全局Session,其生命周期不受单例控制,极易被系统意外回收。
  • InvalidateSession方法操作的是全局默认Session,而非单例自身应持有的Session,导致请求管理逻辑混乱。
  • 未实现请求去重机制,quantity变化时会重复发起相同请求,加剧Session资源冲突,最终引发崩溃。

修复方案

1. 初始化并持有单例专属Session

修改NetworkingClient,创建并持有专属Session,避免依赖全局默认Session:

public static let shared = NetworkingClient()
private var requests = [ApiRequest]()
private var completedRequests = 0
// 初始化单例专属Session,确保全程强引用
private let session: Session = {
    let config = URLSessionConfiguration.default
    config.timeoutIntervalForRequest = 30 // 按需配置超时时间
    return Session(configuration: config)
}()

// 方法名改为小驼峰,符合Swift命名规范
func invalidateSession() {
    // 操作单例自身的Session,而非全局默认Session
    session.session.getAllTasks { tasks in
        tasks.forEach { $0.cancel() }
    }
}

2. 实现请求去重逻辑

在NetworkingClient中新增正在进行的请求管理,避免重复发起相同请求:

// 存储正在进行的请求,用请求唯一标识作为key
private var ongoingRequests = [String: DataRequest]()

func performRequest(requests: [ApiRequest], completion: @escaping (String, Data?, Error?) -> Void) {
    for request in requests {
        // 生成请求唯一标识(可根据path+参数组合生成)
        let requestKey = request.path
        
        // 检查是否已有相同请求在进行,存在则取消旧请求
        if let existingRequest = ongoingRequests[requestKey] {
            existingRequest.cancel()
            ongoingRequests.removeValue(forKey: requestKey)
        }
        
        // 发起新请求并加入管理字典
        let dataRequest = session.request(request.path, method: request.type)
            .responseData { [weak self] response in
                self?.ongoingRequests.removeValue(forKey: requestKey)
                switch response.result {
                case .success(let data):
                    completion(request.path, data, nil)
                case .failure(let error):
                    completion(request.path, nil, error)
                }
            }
        
        ongoingRequests[requestKey] = dataRequest
    }
}

// 供外部检查请求是否正在进行的方法
func isRequestOngoing(key: String) -> Bool {
    return ongoingRequests[key] != nil
}

3. 优化API调用的参数处理

避免直接拼接URL参数,改用Alamofire参数传递方式,同时生成可靠的请求标识:

func gasEstimation(chain_Id: Int, contractAddress: String, fromAddress: String, toAddress: String, tokenId: String, contractType: String, quantity: String) {
    let parameters: [String: Any] = [
        "chainId": chain_Id,
        "contractAddress": contractAddress,
        "from": fromAddress.trimmingCharacters(in: .whitespacesAndNewlines),
        "to": toAddress.trimmingCharacters(in: .whitespacesAndNewlines),
        "type": contractType,
        "quantity": quantity,
        "tokenId": tokenId
    ]
    
    // 生成唯一请求标识:排序参数后拼接,确保相同参数组合生成相同key
    let sortedKeys = parameters.keys.sorted()
    var requestKey = "gasEstimation?"
    for key in sortedKeys {
        if let value = parameters[key] as? String {
            requestKey += "\(key)=\(value)&"
        } else if let value = parameters[key] as? Int {
            requestKey += "\(key)=\(value)&"
        }
    }
    requestKey = String(requestKey.dropLast())
    
    // 检查请求是否正在进行,避免重复调用
    if NetworkingClient.shared.isRequestOngoing(key: requestKey) {
        return
    }
    
    // 注意:需要修改ApiRequest支持参数传递,或者直接用Alamofire的request方法传入parameters
    let apiRequest = ApiRequest(type: .get, path: "gasEstimation", parameters: parameters)
    NetworkingClient.shared.performRequest(requests: [apiRequest]) { path, data, error in
        if let data = data {
            let json = JSON(data)
            let gasTokenModel = NftTransferModel(json: json)
            self.presenter?.onReceiveFee(estimationFee: gasTokenModel, error: nil)
        } else if let error = error {
            let jsonError = JSON(error)
            self.presenter?.onReceiveFee(estimationFee: nil, error: jsonError["message"].stringValue)
        }
    }
}

关键注意事项

  • 必须持有Session强引用:确保Session的生命周期覆盖所有请求时长,避免被提前释放。
  • 统一Session使用:所有请求都通过单例自身的Session发起,不要混合使用全局默认Session。
  • 请求去重逻辑:通过唯一标识管理正在进行的请求,避免同一参数组合重复发起请求。

内容的提问来源于stack exchange,提问作者Muhammad Bilal Raza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:32:05