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

避免重复创建URLSession-dataTask:首次请求返回时共享数据

这个场景我太熟悉了!之前做电商项目的时候,用户反复点击商品详情的刷新按钮,重复创建dataTask不仅占网络带宽,还可能导致界面多次刷新、回调顺序混乱,确实得找个更优雅的解决办法。下面几个方案我亲测好用,你可以根据业务场景选择:

优化重复网络请求的几种实用方案

1. 缓存正在进行的任务,避免重复发起

核心思路是维护一个字典,把当前正在执行的任务和对应的请求标识(比如请求URL+参数的哈希值)绑定。当用户再次发起相同请求时:

  • 如果已有未完成的任务,要么忽略重复请求,要么把新的回调合并到已有任务中(看业务需求)
  • 如果没有,创建新任务并存入字典,任务完成(成功/失败)后从字典移除

举个简单的代码示例:

class NetworkManager {
    static let shared = NetworkManager()
    private var ongoingTasks: [String: URLSessionDataTask] = [:]
    private let session = URLSession.shared
    
    func fetchData(with url: URL, completion: @escaping (Data?, Error?) -> Void) {
        // 如果请求带参数,建议把参数转成字符串一起生成key,避免不同参数的请求被误判为相同
        let taskKey = url.absoluteString
        
        // 检查是否有正在进行的相同请求
        if ongoingTasks[taskKey] != nil {
            print("已有相同请求在处理,忽略本次重复请求")
            return
        }
        
        let task = session.dataTask(with: url) { [weak self] data, _, error in
            // 任务完成后从缓存移除
            self?.ongoingTasks.removeValue(forKey: taskKey)
            completion(data, error)
        }
        ongoingTasks[taskKey] = task
        task.resume()
    }
}

如果需要让多个相同请求都拿到结果,可以把ongoingTasks的value改成包含任务和回调数组的结构体,任务完成时遍历所有回调执行。

2. 取消旧任务,只保留最新请求

如果是像搜索框输入这类场景——用户快速输入时,只需要最后一次输入的搜索结果,那可以在发起新请求时直接取消之前未完成的同类任务:

class SearchNetworkManager {
    static let shared = SearchNetworkManager()
    private var currentSearchTask: URLSessionDataTask?
    private let session = URLSession.shared
    
    func search(query: String, completion: @escaping (Data?, Error?) -> Void) {
        // 先取消之前的搜索任务
        currentSearchTask?.cancel()
        
        guard let url = URL(string: "https://your-api.com/search?q=\(query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)") else {
            completion(nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil))
            return
        }
        
        let task = session.dataTask(with: url) { [weak self] data, _, error in
            // 取消的任务会返回URLError.cancelled,这里可以跳过错误处理
            if let error = error as? URLError, error.code == .cancelled {
                return
            }
            self?.currentSearchTask = nil
            completion(data, error)
        }
        currentSearchTask = task
        task.resume()
    }
}

这种方案能确保用户只拿到最新的请求结果,避免旧结果覆盖新结果的尴尬。

3. 结合URLSession内置缓存减少重复请求

如果你的请求是GET类型、数据不会频繁更新,可以利用URLSession的缓存策略,让系统自动处理重复请求:

// 配置请求缓存策略:优先用缓存,没有缓存再发起网络请求
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30)
let task = session.dataTask(with: request) { data, response, error in
    // 处理结果
}
task.resume()

不过这种方式依赖服务器返回的缓存头(比如Cache-Control),适合数据更新不频繁的场景,不能完全解决“正在进行的重复请求”问题,但能减少后续相同请求的网络开销。

4. 封装自定义任务,实现回调合并(进阶方案)

如果需要更灵活的任务管理,可以封装一个自定义的RequestTask类,管理每个请求的状态和回调数组,支持多个请求共享同一个网络任务:

class RequestTask {
    let task: URLSessionDataTask
    var completions: [(Data?, Error?) -> Void] = []
    
    init(task: URLSessionDataTask, completion: @escaping (Data?, Error?) -> Void) {
        self.task = task
        self.completions.append(completion)
    }
}

class AdvancedNetworkManager {
    static let shared = AdvancedNetworkManager()
    private var ongoingTasks: [String: RequestTask] = [:]
    private let session = URLSession.shared
    
    func fetchData(with url: URL, completion: @escaping (Data?, Error?) -> Void) {
        let taskKey = url.absoluteString
        
        if let existingTask = ongoingTasks[taskKey] {
            // 把新的回调添加到已有任务中
            existingTask.completions.append(completion)
            return
        }
        
        let task = session.dataTask(with: url) { [weak self] data, _, error in
            guard let self = self, let requestTask = self.ongoingTasks.removeValue(forKey: taskKey) else {
                return
            }
            // 遍历所有回调执行,让所有发起请求的地方都拿到结果
            requestTask.completions.forEach { $0(data, error) }
        }
        let requestTask = RequestTask(task: task, completion: completion)
        ongoingTasks[taskKey] = requestTask
        task.resume()
    }
}

这种方案能最大化节省网络资源,同时保证所有发起请求的页面或组件都能获取到数据。


总结一下:

  • 要避免重复请求,优先选缓存正在进行的任务方案,根据业务决定忽略重复请求还是合并回调
  • 实时性要求高的场景(比如搜索),用取消旧任务的方案更合适
  • 数据更新不频繁的场景,可以配合URLSession缓存策略做辅助优化

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:40:14