You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 20:57:19