Swift 4中如何实现两个并发API调用并在全部完成后更新UI?
嘿,我来帮你搞定这个并发API调用的问题~你的思路方向是对的,用OperationQueue来做并发没问题,但原代码缺少了跟踪两个API都成功完成的逻辑,所以没法触发停止指示器和刷新表格的操作。我给你两种靠谱的实现方案,你可以根据需求选:
方案一:用DispatchGroup(简洁直观,推荐)
DispatchGroup是GCD里专门用来跟踪多个异步任务完成的工具,非常适合这种场景。我们可以用它来监听两个API都成功完成,然后统一处理UI更新:
func downloadDetails() { // 先在主线程启动活动指示器(UI操作必须在主线程!) DispatchQueue.main.async { self.activityIndicator.startAnimating() self.tableView.isUserInteractionEnabled = false // 可选:防止用户操作 } // 定义两个变量存储接口返回的数据(根据你的实际类型调整) var fetchedAData: Any? var fetchedBData: Any? let group = DispatchGroup() // 第一个API调用 group.enter() WebServiceManager.getAData(format: A) { data, error in defer { group.leave() } // 不管成功失败,都告诉group任务结束 guard let successData = data, error == nil else { // 这里可以处理A接口失败的情况,比如打印日志或提示用户 print("Failed to fetch A data: \(error?.localizedDescription ?? "Unknown error")") return } fetchedAData = successData // 这里可以做A数据的预处理,比如转模型 } // 第二个API调用 group.enter() WebServiceManager.getBData(format: B) { data, error in defer { group.leave() } guard let successData = data, error == nil else { print("Failed to fetch B data: \(error?.localizedDescription ?? "Unknown error")") return } fetchedBData = successData // 预处理B数据 } // 当两个任务都结束时,执行统一逻辑 group.notify(queue: .main) { [weak self] in guard let self = self, fetchedAData != nil, fetchedBData != nil else { // 至少一个接口失败,停止指示器并提示用户 self?.activityIndicator.stopAnimating() self?.tableView.isUserInteractionEnabled = true // 可选:显示错误弹窗 return } // 两个接口都成功!停止指示器+刷新表格 self.activityIndicator.stopAnimating() self.tableView.isUserInteractionEnabled = true self.tableView.reloadData() } }
方案二:用OperationQueue+线程安全计数(适合复杂场景)
如果你需要更灵活的任务管理(比如中途取消任务、添加依赖),可以继续用OperationQueue,但要加一个线程安全的计数器来跟踪成功次数:
func downloadDetails() { DispatchQueue.main.async { self.activityIndicator.startAnimating() self.tableView.isUserInteractionEnabled = false } let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 2 // 允许同时跑两个任务 var successCount = 0 let countLock = NSLock() // 保证多线程下计数安全 var fetchedAData: Any? var fetchedBData: Any? let operation1 = BlockOperation { [weak self] in WebServiceManager.getAData(format: A) { data, error in guard let successData = data, error == nil else { // A接口失败,检查是否两个都失败了,是的话停止指示器 countLock.lock() let bothFailed = successCount == 0 countLock.unlock() if bothFailed { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() self?.tableView.isUserInteractionEnabled = true } } return } fetchedAData = successData // 更新成功计数 countLock.lock() successCount += 1 let allSuccess = successCount == 2 countLock.unlock() if allSuccess { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() self?.tableView.isUserInteractionEnabled = true self?.tableView.reloadData() } } } } let operation2 = BlockOperation { [weak self] in WebServiceManager.getBData(format: B) { data, error in guard let successData = data, error == nil else { countLock.lock() let bothFailed = successCount == 0 countLock.unlock() if bothFailed { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() self?.tableView.isUserInteractionEnabled = true } } return } fetchedBData = successData countLock.lock() successCount += 1 let allSuccess = successCount == 2 countLock.unlock() if allSuccess { DispatchQueue.main.async { self?.activityIndicator.stopAnimating() self?.tableView.isUserInteractionEnabled = true self?.tableView.reloadData() } } } } operationQueue.addOperations([operation1, operation2], waitUntilFinished: false) }
关键注意点:
- UI操作必须在主线程:活动指示器的启动/停止、表格刷新都要放在
DispatchQueue.main.async里,否则会崩溃。 - 内存泄漏防护:用
[weak self]捕获self,避免循环引用(因为网络回调会持有self,而self持有OperationQueue/DispatchGroup)。 - 错误处理:不要忽略错误情况,比如其中一个接口失败时,要停止指示器并给用户反馈。
- 线程安全:如果用计数器,一定要加锁(比如
NSLock),因为两个回调可能同时更新计数,导致线程冲突。
这样修改后,就能实现你要的“两个API并发执行,都成功后停止指示器+刷新表格”的需求啦~
内容的提问来源于stack exchange,提问作者King
相关产品推荐
相关产品推荐

