切换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()的放置位置。
解答
疑问解答
是否会存在多个活跃请求?
是的。每次切换到View B都会初始化新的View2ViewModel实例,每个实例都会启动独立的Task发起API请求。这些Task的生命周期独立于ViewModel,不会因为旧ViewModel销毁而自动取消,频繁切换会导致多组请求同时在后台运行。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
相关产品推荐
相关产品推荐

