iOS开发:如何在Alamofire网络请求完成后推送视图控制器?
解决Alamofire网络请求与视图控制器推送的竞态条件问题
看起来你的问题核心是网络请求填充单例的操作和首页视图控制器加载数据的时机不匹配——当你推送首页VC时,单例的数据还没完全写入,导致首次加载无数据;重新加载时数据已经就绪,所以能正常展示。下面给你几个针对性的解决方案和代码优化思路:
一、先修正当前代码的核心问题
你的嵌套回调里有两个容易踩坑的点:UI操作没确保在主线程执行、getUserInfo的回调可能没等数据完全写入单例就推送VC。先把代码改成这样:
guard let username = emailTextField.text else { return } guard let password = passwordTextField.text else { return } UIApplication.shared.beginIgnoringInteractionEvents() activityIndicator.startAnimating() // 记得启动指示器,你之前的代码里漏了这一步 TFTINetworkManager.shared().login(username: username, password: password) { [weak self] loginSuccessful in guard let self = self else { return } self.activityIndicator.stopAnimating() // 所有UI操作必须回到主线程执行 DispatchQueue.main.async { UIApplication.shared.endIgnoringInteractionEvents() if loginSuccessful { TFTINetworkManager.shared().getUserInfo { [weak self] isInfoLoaded in guard let self = self else { return } // 先确保用户信息成功写入单例,再推送VC guard isInfoLoaded else { let alert = UIAlertController(title: "加载失败", message: "无法获取用户信息,请重试", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "关闭", style: .default)) self.present(alert, animated: true) return } // 推送VC的操作也要放在主线程 DispatchQueue.main.async { let homeVC = TFTIHomeFeedVC.createFromStoryboard() self.navigationController?.pushViewController(homeVC, animated: true) } } } else { let alert = UIAlertController(title: "登录失败", message: "请检查账号密码是否正确", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "关闭", style: .default)) self.present(alert, animated: true) } } }
二、关键优化细节说明
强制主线程执行UI操作
Alamofire的网络回调默认是在后台线程的,如果直接在回调里做push、弹alert这类UI操作,不仅可能导致线程崩溃,还会出现数据同步延迟的问题——主线程的VC生命周期方法可能比后台线程的单例数据写入先执行,自然拿不到数据。确保
getUserInfo的回调时机正确
你需要检查TFTINetworkManager里getUserInfo的实现,必须满足:- 网络请求成功拿到数据后,再将数据写入单例
- 只有当数据完全写入单例(并且处理好线程安全)后,才调用回调闭包,传递
isInfoLoaded = true - 如果请求失败,要传递
isInfoLoaded = false,并在回调里处理错误
单例的线程安全处理
如果你的单例是多线程读写的,一定要用串行队列来保护数据,比如:
class TFTIDataStore { static let shared = TFTIDataStore() private let queue = DispatchQueue(label: "com.yourapp.datastore.queue") private var _userInfo: UserInfo? var userInfo: UserInfo? { get { return queue.sync { _userInfo } } set { queue.async { self._userInfo = newValue } } } }
这样不管是后台线程写入还是主线程读取,都不会出现数据竞态。
三、如果有多个后续请求的情况
如果登录成功后需要调用多个网络请求(比如同时获取用户信息、系统设置等),可以用DispatchGroup来等待所有请求完成后再推送VC:
if loginSuccessful { let group = DispatchGroup() // 请求用户信息 group.enter() TFTINetworkManager.shared().getUserInfo { success in // 写入单例操作 group.leave() } // 请求系统设置 group.enter() TFTINetworkManager.shared().getAppSettings { success in // 写入单例操作 group.leave() } // 所有请求完成后,在主线程推送VC group.notify(queue: .main) { [weak self] in guard let self = self else { return } let homeVC = TFTIHomeFeedVC.createFromStoryboard() self.navigationController?.pushViewController(homeVC, animated: true) } }
这样就能彻底避免竞态条件,确保首页VC推送时所有需要的数据都已经准备就绪了。
内容的提问来源于stack exchange,提问作者Alexis Candelaria
相关产品推荐
相关产品推荐

