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

切换Tab时取消运行中Task的实现方案咨询

问题:Tab切换时取消依赖型API请求的实现方案

场景与需求

  • 应用包含View A和View B两个Tab视图
  • 切换到View B时发起两个存在依赖关系的API请求(第二个请求依赖第一个的结果)
  • 切回View A时需取消这两个请求,避免频繁切换导致多组请求同时运行

现有代码示例

class View2ViewModel{
    let apiService:APIService

    init(apiService:APIService){
        self.apiService = apiService
        executeTask()
    }

    func executeTask(){
        Task {
            do{
                let firstResult = try await apiService.getTestData(for: "First test url")
                // Second depends on first
                let secondResult = try await apiService.getTestData1(for: "Second test url", with: firstResult)
            }catch{
                print("error")
            }
        }
    }
}

class APIService{
    let networkManager:NetworkManager

    init(networkManager:NetworkManager){
        self.networkManager = networkManager
    }

    func getTestData(for Url:String) async throws -> [String] {
        guard let url = URL(string: Url) else{
            return []
        }
        let data =  try await networkManager.performDataTaskAsync(with:URLRequest(url:url))
        return try JSONDecoder().decode([String].self,from:data)
    }

    func getTestData1(for Url:String,with requestData:[String]) async throws -> [String] {
        guard let url = URL(string: Url) else{
            return []
        }
        // let's assume i crated a URLRequest with requestData
        let data =  try await networkManager.performDataTaskAsync(with:URLRequest(url:url))
        return try JSONDecoder().decode([String].self,from:data)
    }
}

class NetworkManager{
    enum NetworkError:Error{
        case connectivity
    }
    public func performDataTaskAsync(with request:URLRequest) async throws -> Data {
        // Use the async variant of URLSession to fetch data
        // Code might suspend here
        let successResponseRange = (200...299)
        let (data, response) = try await URLSession.shared.data(for: request)
        if let response = response as? HTTPURLResponse,
           !successResponseRange.contains(response.statusCode) {
            if let logs = String(data: data, encoding: .utf8) {
                APILogs.logsText.append("\n\(logs)")
            }
            throw NetworkError.connectivity
        }
        return data
    }
}

疑问

  • 多次切换Tab(8-9次)时,是否会存在多个活跃的API请求?
  • View2ViewModel对象销毁时,内部的Task会自动取消吗?

核心诉求

在View B页面仅保留一个活跃的API请求,取消其他所有运行中的任务;同时希望明确Task.Cancellation机制的适配方式及task.cancel()的放置位置。


解答

疑问解答

  1. 是否会存在多个活跃请求?
    是的。每次切换到View B都会初始化新的View2ViewModel实例,每个实例都会启动独立的Task发起API请求。这些Task的生命周期独立于ViewModel,不会因为旧ViewModel销毁而自动取消,频繁切换会导致多组请求同时在后台运行。

  2. ViewModel销毁时Task会自动取消吗?
    不会。Swift的Task默认不会随着创建它的对象销毁而自动取消,必须显式调用cancel()方法才能终止任务。

最优实现方案

要实现View B仅保留一个活跃请求,同时在切回View A时取消请求,需要从Task引用管理、取消信号响应和生命周期绑定三个维度处理:

1. 在ViewModel中持有并管理Task引用

修改View2ViewModel,将Task存储为实例属性,实现任务的取消与替换逻辑:

class View2ViewModel{
    let apiService:APIService
    private var runningTask: Task<Void, Error>? // 持有当前运行的Task引用

    init(apiService:APIService){
        self.apiService = apiService
        executeTask()
    }

    func executeTask(){
        // 先取消之前可能存在的任务,确保同一时间只有一个活跃请求
        runningTask?.cancel()
        // 创建新任务并保存引用,使用weak self避免循环引用
        runningTask = Task { [weak self] in
            guard let self = self else { return }
            do{
                let firstResult = try await self.apiService.getTestData(for: "First test url")
                // 主动检查任务是否已取消,避免执行不必要的后续请求
                try Task.checkCancellation()
                let secondResult = try await self.apiService.getTestData1(for: "Second test url", with: firstResult)
            }catch is CancellationError {
                print("任务已取消")
            }catch{
                print("请求错误: \(error)")
            }
        }
    }

    // ViewModel销毁时主动取消任务
    deinit {
        runningTask?.cancel()
    }
}

2. 在API层响应取消信号

  • URLSession的data(for:)异步方法会自动监听Task的取消信号,一旦任务取消会立即抛出CancellationError,因此你的NetworkManager代码无需额外修改。
  • 在第一个请求完成后、第二个请求发起前调用try Task.checkCancellation(),可以主动终止后续流程,避免做无用功。

3. 绑定View生命周期(可选但推荐)

如果是SwiftUI视图,可在onDisappear回调中主动取消任务,确保切回View A时立即终止请求:

struct ViewB: View {
    @StateObject var viewModel: View2ViewModel

    var body: some View {
        // 你的View内容
        .onDisappear {
            viewModel.runningTask?.cancel()
        }
    }
}

如果是UIKit,可在viewWillDisappear方法中调用取消逻辑。

关键机制说明

  • Task取消逻辑:调用task.cancel()会向Task发送取消信号,try await的异步操作会监听该信号并抛出错误;Task.checkCancellation()是主动检查取消状态,同步抛出CancellationError。
  • 避免循环引用:Task闭包中使用[weak self],防止ViewModel被Task强引用导致无法销毁。
  • 重复请求控制:每次调用executeTask()时先取消旧任务,确保同一时间只有一个活跃请求在运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 10:45:09