如何解决Swift API调用中Firebase getIDToken返回nil的问题
问题根因
- 当前实现的
token()函数是典型的“异步操作同步取值”错误实现:getIDToken的完成回调是异步调度执行的,你在发起Token请求后立刻执行return result,这个时机回调还没有被触发,result永远是初始的nil值。 - 额外存在两个实现问题:
- 对
Auth.auth().currentUser做了强制解包,用户未登录时会直接触发崩溃 - Token获取成功的分支错误打印了
err,属于代码笔误,成功场景下err本身就是nil
- 对
代码修改方案
1. 重构SessionData的Token获取方法
彻底废弃同步返回的写法,改为通过完成回调传递Token获取结果,同时增加登录状态、Token为空的容错判断:
class SessionData : ObservableObject { // 原同步token()方法替换为带错误处理的异步回调方法 func getIDToken(completion: @escaping (Result<String, Error>) -> Void) { // 先校验当前登录态,避免强制解包崩溃 guard let currentUser = Auth.auth().currentUser else { completion(.failure(NSError( domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "当前账号未登录"] ))) return } currentUser.getIDToken { (token, error) in if let error = error { print("*** TOKEN获取失败: \(error.localizedDescription)") completion(.failure(error)) return } guard let validToken = token else { completion(.failure(NSError( domain: "AuthError", code: -2, userInfo: [NSLocalizedDescriptionKey: "获取到的Token为空"] ))) return } print("*** TOKEN获取成功") completion(.success(validToken)) } } }
2. 调整FetchPosts的加载逻辑
把原来直接拼URL的步骤,挪到Token获取成功的回调内部执行,保证异步流程的先后顺序正确:
func load(api: Bool, session: SessionData) { if api { // 先异步获取Token,拿到合法Token后再发起网络请求 session.getIDToken { [weak self] result in guard let self = self else { return } switch result { case .success(let token): // Token校验通过后再拼接接口地址 guard let url = URL(string: MyAPI.getAddress(token: token)) else { DispatchQueue.main.async { session.json_error(error: "接口地址格式非法") } return } // 原有的网络请求逻辑保持不变 URLSession.shared.dataTask(with: url) {(data, response, error) in do { if let postsData = data { let decodedData = try JSONDecoder().decode(Response.self, from: postsData) DispatchQueue.main.async { self.posts = decodedData.result if decodedData.error != nil { print("ERROR: \(decodedData.error!)") session.json_error(error: decodedData.error!) } } } else { print("No data. Connection error.") DispatchQueue.main.async { session.json_error(error: "Could not connect to server, please try again!") } } } catch { print("* Error: \(error)") } }.resume() case .failure(let error): // Token获取失败时直接触发错误提示 DispatchQueue.main.async { session.json_error(error: "身份校验失败,请重新登录:\(error.localizedDescription)") } } } } else { // 原有的本地测试JSON加载逻辑无需修改 let url = Bundle.main.url(forResource: "test", withExtension: "json")! let data = try! Data(contentsOf: url) let decoder = JSONDecoder() if let products = try? decoder.decode([Post].self, from: data) { self.posts = products } } }
落地注意事项
- 不要尝试用信号量、锁之类的方式把异步Token获取强行改成同步等待,这类写法会阻塞当前线程,一旦在主线程调用就会造成界面卡死
- 所有涉及更新
@Published发布属性、调用错误提示的逻辑,必须切回主线程执行,避免触发UI更新的线程警告 - 原调用
posts.load(...)的位置不需要做任何修改,整个流程的异步嵌套对上层是完全透明的
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

