Firebase异步请求致posts数组为空,如何在闭包外使用该数组?
问题原因
Firebase的observeSingleEvent是异步执行的网络请求操作——你的print(posts)代码会在闭包内的请求完成、数组填充前就执行,所以打印结果为空。同时你当前的代码是嵌套遍历用户与帖子,每个用户的帖子请求都是独立异步的,必须等所有异步请求全部完成,才能拿到完整的posts数组。
解决方案
方案1:使用完成回调闭包(最通用的方式)
修改fetchPosts方法,通过回调闭包返回填充完成的数组,同时用DispatchGroup等待所有异步请求结束:
class HomeViewController: UIViewController { var posts = [Post]() override func viewDidLoad() { super.viewDidLoad() fetchPosts { [weak self] fetchedPosts in guard let self = self else { return } self.posts = fetchedPosts print(self.posts) // 此处可拿到完整数组 // 这里可以执行UI刷新操作,比如tableView.reloadData() } } func fetchPosts(completion: @escaping ([Post]) -> Void) { let userDBRef = Database.database().reference().child("users") let postRef = Database.database().reference().child("posts") var allPosts = [Post]() let dispatchGroup = DispatchGroup() userDBRef.observeSingleEvent(of: .value) { snapshot in guard let userDict = snapshot.value as? [String: Any] else { completion(allPosts) return } userDict.forEach { key, value in dispatchGroup.enter() // 标记开始一个异步请求 guard let userData = value as? [String: Any] else { dispatchGroup.leave() // 请求失败也要标记结束 return } let user = User(uid: key, dictionary: userData) postRef.child(user.uid).observeSingleEvent(of: .value) { snapshot in defer { dispatchGroup.leave() } // 请求结束后标记完成 guard let postDict = snapshot.value as? [String: Any] else { return } postDict.forEach { _, postValue in guard let postData = postValue as? [String: Any] else { return } let post = Post(user: user, dictionary: postData) allPosts.append(post) } } } // 所有异步请求完成后,通过回调返回结果 dispatchGroup.notify(queue: .main) { completion(allPosts) } } } }
方案2:iOS 13+ 使用Combine框架
如果你的项目支持iOS 13及以上,可以用Combine的Future处理异步流程:
import Combine class HomeViewController: UIViewController { var posts = [Post]() var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() fetchPosts() .receive(on: DispatchQueue.main) .sink(receiveCompletion: { completion in if case .failure(let error) = completion { print("获取帖子失败:\(error)") } }, receiveValue: { [weak self] fetchedPosts in self?.posts = fetchedPosts print(self?.posts ?? []) // 刷新UI }) .store(in: &cancellables) } func fetchPosts() -> AnyPublisher<[Post], Error> { return Future { promise in let userDBRef = Database.database().reference().child("users") let postRef = Database.database().reference().child("posts") var allPosts = [Post]() let dispatchGroup = DispatchGroup() userDBRef.observeSingleEvent(of: .value) { snapshot in guard let userDict = snapshot.value as? [String: Any] else { promise(.failure(NSError(domain: "FirebaseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "获取用户数据失败"]))) return } userDict.forEach { key, value in dispatchGroup.enter() guard let userData = value as? [String: Any] else { dispatchGroup.leave() return } let user = User(uid: key, dictionary: userData) postRef.child(user.uid).observeSingleEvent(of: .value) { snapshot in defer { dispatchGroup.leave() } guard let postDict = snapshot.value as? [String: Any] else { return } postDict.forEach { _, postValue in guard let postData = postValue as? [String: Any] else { return } let post = Post(user: user, dictionary: postData) allPosts.append(post) } } } dispatchGroup.notify(queue: .main) { promise(.success(allPosts)) } } } .eraseToAnyPublisher() } }
额外优化建议
- 替换所有强制解包(
as!)为可选绑定,避免数据格式异常时崩溃 - 可以优化Firebase的数据结构,比如将帖子直接关联用户ID存储,减少嵌套请求的数量
- 闭包内使用
weak self避免循环引用
内容的提问来源于stack exchange,提问作者Iksang Yoo
相关产品推荐
相关产品推荐

