如何确保所有网络请求完成后再访问Core Data并跳转视图控制器?
解决异步API请求未完成就跳转导致Core Data为空的问题
你猜的完全没错!问题就出在这些异步API请求是并行执行的——apiCallFive可能在apiCallOne到apiCallFour完成前就触发了跳转,导致ViewController B加载时,Core Data还没拿到前面请求的数据,自然显示为空。
要实现所有请求完成后再跳转,最常用也最简洁的方案是用DispatchGroup来管理所有异步任务,下面是具体实现:
方案一:用DispatchGroup等待所有前置请求完成
我们可以用DispatchGroup的enter()和leave()来标记每个任务的开始和结束,等所有任务都完成后,再执行apiCallFive和跳转逻辑:
Api.verifyOtp(email, otp).continueWith { (task) -> Any? in if task.succeed { // 创建一个DispatchGroup实例 let taskGroup = DispatchGroup() // 给每个前置请求标记任务开始/结束 taskGroup.enter() self.apiCallOne().continueWith { _ in // 请求完成后标记任务结束 taskGroup.leave() } taskGroup.enter() self.apiCallTwo().continueWith { _ in taskGroup.leave() } taskGroup.enter() self.apiCallThree().continueWith { _ in taskGroup.leave() } taskGroup.enter() self.apiCallFour().continueWith { _ in taskGroup.leave() } // 等待所有前置任务完成后,在主队列执行后续逻辑(跳转UI必须在主队列) taskGroup.notify(queue: .main) { // 现在执行apiCallFive,完成后跳转 Api.apiCallFive().continueOnSuccessWith { (task) -> Any? in Hud.hide() if task.succeed { let storyboard = UIStoryboard(name: "Main", bundle: nil) let viewB = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") self.navigationController?.pushViewController(viewB, animated: true) } return nil } } } else { Hud.hide() task.showError() } return nil }
关键说明:
enter()和leave()必须成对出现:每调用一次enter(),就必须在任务完成(无论成功或失败)时调用一次leave(),否则notify永远不会触发。notify(queue: .main)确保跳转逻辑在主队列执行,符合iOS UI操作的线程要求。- 如果需要确保所有前置请求都成功才执行跳转,可以在每个
continueWith里判断任务状态,失败时处理错误并调用leave(),比如:self.apiCallOne().continueWith { task in if !task.succeed { task.showError() } taskGroup.leave() }
方案二:用OperationQueue管理任务依赖
如果需要更精细的任务控制(比如设置并行数、取消任务等),可以用OperationQueue给任务添加依赖,让apiCallFive的跳转任务必须等待前四个请求完成:
Api.verifyOtp(email, otp).continueWith { (task) -> Any? in if task.succeed { let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 4 // 允许4个任务并行 // 封装每个API请求为BlockOperation(注意用信号量把异步转同步) let op1 = BlockOperation { let semaphore = DispatchSemaphore(value: 0) self.apiCallOne().continueWith { _ in semaphore.signal() } semaphore.wait() } let op2 = BlockOperation { let semaphore = DispatchSemaphore(value: 0) self.apiCallTwo().continueWith { _ in semaphore.signal() } semaphore.wait() } let op3 = BlockOperation { let semaphore = DispatchSemaphore(value: 0) self.apiCallThree().continueWith { _ in semaphore.signal() } semaphore.wait() } let op4 = BlockOperation { let semaphore = DispatchSemaphore(value: 0) self.apiCallFour().continueWith { _ in semaphore.signal() } semaphore.wait() } // 封装跳转任务(必须在主队列执行UI操作) let jumpOp = BlockOperation { DispatchQueue.main.async { Api.apiCallFive().continueOnSuccessWith { (task) -> Any? in Hud.hide() if task.succeed { let storyboard = UIStoryboard(name: "Main", bundle: nil) let viewB = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") self.navigationController?.pushViewController(viewB, animated: true) } return nil } } } // 设置跳转任务依赖于前四个请求任务 jumpOp.addDependency(op1) jumpOp.addDependency(op2) jumpOp.addDependency(op3) jumpOp.addDependency(op4) // 添加任务到队列执行 operationQueue.addOperations([op1, op2, op3, op4], waitUntilFinished: false) OperationQueue.main.addOperation(jumpOp) } else { Hud.hide() task.showError() } return nil }
额外注意事项
- 确保Core Data的写入操作线程安全:如果API回调在后台队列,要使用对应的Core Data上下文,避免直接在后台队列操作主上下文,否则可能导致数据写入延迟或崩溃。
- 如果你的API框架支持Promise风格的链式调用(比如PromiseKit),也可以用
when(fulfilled:)来组合多个异步任务,写法会更简洁。
内容的提问来源于stack exchange,提问作者butter_baby
相关产品推荐
相关产品推荐

