Swift中如何将Firebase获取的数据保存到全局变量?
解决Swift中Firebase异步数据无法保存到全局变量的问题
嘿,这个问题我太熟悉啦! Firebase 的数据获取是异步操作,这就是你闭包外的 spendDict 一直为空的核心原因——你可以把它类比成:你喊了一份外卖(调用 retrieveSpend()),然后立刻跑到餐桌找饭(访问全局的 spendDict),但这时候外卖还在路上,餐桌当然是空的!只有等外卖送到(Firebase 请求完成,闭包执行),你才能拿到饭(数据)。
下面给你几个实用的解决方案,从基础到进阶都有:
方案1:用Completion Handler(闭包回调)——兼容所有iOS版本
这是最基础的异步处理方式,我们给 retrieveSpend() 加一个回调闭包,等数据获取完成后,再通过闭包把结果传出去,在回调里更新全局变量:
// 全局变量定义 var spendDict: [String: Any] = [:] // 修改你的retrieveSpend函数,添加completion回调 func retrieveSpend(completion: @escaping ([String: Any]?) -> Void) { let db = Firestore.firestore() db.collection("spend").getDocuments { snapshot, error in if let error = error { print("获取数据失败:\(error.localizedDescription)") completion(nil) return } guard let documents = snapshot?.documents else { completion(nil) return } // 解析Firebase返回的文档数据到字典 var resultDict: [String: Any] = [:] for doc in documents { resultDict[doc.documentID] = doc.data() } // 数据准备好后,调用回调传递结果 completion(resultDict) } } // 调用方式:在回调里更新全局变量 retrieveSpend { [weak self] fetchedDict in guard let validDict = fetchedDict else { return } // 此时数据已经拿到,放心更新全局变量 self?.spendDict = validDict // 所有需要用到spendDict的逻辑,都要放在这个闭包里执行 print("更新后的spendDict:\(self?.spendDict ?? [:])") }
方案2:用Async/Await(iOS 15+ / macOS 12+)——Swift推荐的简洁方式
如果你的项目支持iOS 15及以上,用Swift的原生异步语法 async/await 会更清爽,代码逻辑更像同步写法:
// 全局变量定义 var spendDict: [String: Any] = [:] // 把retrieveSpend改成async函数 func retrieveSpend() async throws -> [String: Any] { let db = Firestore.firestore() // 用await等待Firebase请求完成 let snapshot = try await db.collection("spend").getDocuments() var resultDict: [String: Any] = [:] for doc in snapshot.documents { resultDict[doc.documentID] = doc.data() } return resultDict } // 调用时要在异步环境中(比如Task里) Task { do { // 用await等待数据返回,然后直接赋值给全局变量 spendDict = try await retrieveSpend() print("更新后的spendDict:\(spendDict)") // 后续操作放在这里执行 } catch { print("获取数据失败:\(error.localizedDescription)") } }
方案3:用Combine框架——响应式编程场景
如果你的项目用了Combine进行响应式开发,可以把数据获取封装成Publisher,订阅后更新全局变量:
import Combine // 全局变量定义 var spendDict: [String: Any] = [:] // 用来管理订阅生命周期的集合 var cancellables = Set<AnyCancellable>() func retrieveSpend() -> AnyPublisher<[String: Any], Error> { let db = Firestore.firestore() return Future { promise in db.collection("spend").getDocuments { snapshot, error in if let error = error { promise(.failure(error)) return } guard let documents = snapshot?.documents else { let noDataError = NSError(domain: "FirebaseDataError", code: 0, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"]) promise(.failure(noDataError)) return } var resultDict: [String: Any] = [:] for doc in documents { resultDict[doc.documentID] = doc.data() } promise(.success(resultDict)) } }.eraseToAnyPublisher() } // 订阅Publisher并更新全局变量 retrieveSpend() .receive(on: DispatchQueue.main) // 如果要更新UI,记得切换到主线程 .sink( receiveCompletion: { completion in if case .failure(let error) = completion { print("数据获取失败:\(error)") } }, receiveValue: { [weak self] fetchedDict in self?.spendDict = fetchedDict print("更新后的spendDict:\(self?.spendDict ?? [:])") } ) .store(in: &cancellables)
关键提醒
不管用哪种方案,核心原则都是:必须等待Firebase的异步请求完成后,再去访问或更新spendDict。永远不要在调用retrieveSpend()后立刻同步访问全局变量,因为此时异步请求还没结束,数据还没回来。
内容的提问来源于stack exchange,提问作者FYY
相关产品推荐
相关产品推荐

