Firebase实时数据库遍历用户ID数组获取数据时数组为空崩溃问题
解决Firebase Realtime Database批量查询用户数据的异步问题
看起来你遇到的核心问题是异步操作的执行顺序和数组初始化不当导致的,我来一步步帮你梳理和解决:
问题分析
- 数组初始化错误:你定义了
var users: [User]?,初始值是nil,后续调用users?.append(user)时,因为可选链的特性,nil调用方法不会有任何效果,数组始终是空的甚至还是nil,最后users!强制解包直接导致崩溃。 - 异步请求的执行时机:Firebase的
observeSingleEvent是异步操作,for循环里发起的多个查询请求不会立刻执行回调,而是在后台完成后才会触发。但你在循环结束后立刻执行了创建Group和跳转的代码,这时候所有用户数据的回调还没跑完,users自然没有数据。
解决方案一:用DispatchGroup等待所有异步请求完成
这是最直接的修改方式,通过DispatchGroup来跟踪所有用户查询的完成状态,等全部请求结束后再处理Group和跳转逻辑:
guard let groupChatPartnerId = message.chatPartnerId() else { return } // 初始化空数组,避免nil问题 var users = [User]() let ref = Database.database().reference().child("groups").child(groupChatPartnerId) ref.observeSingleEvent(of: .value, with: { (snapshot) in let groupId = snapshot.key let groupName = snapshot.childSnapshot(forPath: "groupName").value as! String let userIdDictionary = snapshot.childSnapshot(forPath: "users").value as! Dictionary<String,AnyObject> let userIds = Array(userIdDictionary.keys) print("userIds: ", userIds) // 创建DispatchGroup let dispatchGroup = DispatchGroup() for userId in userIds { print("This should be the individual userId: ", userId) let userRef = Database.database().reference().child("users").child(userId) // 进入group dispatchGroup.enter() userRef.observeSingleEvent(of: .value, with: { (snapshot) in defer { // 离开group,不管请求成功失败都要调用 dispatchGroup.leave() } print("This is the snapshot: ", snapshot) guard let email = snapshot.childSnapshot(forPath: "email").value as? String, let username = snapshot.childSnapshot(forPath: "username").value as? String, let profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as? String else { // 处理数据异常的情况,比如某个用户数据缺失 return } let uid = snapshot.key let user = User(uid: uid, userUsername: username, userProfileImageUrl: profileImageUrl, userEmail: email) users.append(user) print("user to append to users: ", user) }, withCancel: { error in // 处理请求失败的情况 print("Failed to fetch user: \(error.localizedDescription)") dispatchGroup.leave() }) } // 所有请求完成后触发 dispatchGroup.notify(queue: .main) { print("users :", users) let group = Group(groupId: groupId, groupName: groupName, users: users) self.showChatControllerForGroup(group: group) } }, withCancel: nil)
解决方案二:批量查询优化(更高效)
上面的循环查询会发起多个网络请求,我们可以利用Firebase的批量查询能力,一次请求获取所有用户数据,减少网络开销:
guard let groupChatPartnerId = message.chatPartnerId() else { return } var users = [User]() let ref = Database.database().reference().child("groups").child(groupChatPartnerId) ref.observeSingleEvent(of: .value, with: { (snapshot) in let groupId = snapshot.key let groupName = snapshot.childSnapshot(forPath: "groupName").value as! String let userIdDictionary = snapshot.childSnapshot(forPath: "users").value as! Dictionary<String,AnyObject> let userIds = Array(userIdDictionary.keys) print("userIds: ", userIds) // 批量查询所有用户 let usersRef = Database.database().reference().child("users") usersRef.queryOrderedByKey().query(in: userIds).observeSingleEvent(of: .value, with: { usersSnapshot in guard let usersDict = usersSnapshot.value as? [String: [String: Any]] else { // 处理数据异常 print("No users data found") return } // 遍历用户字典转成User对象 for (uid, userData) in usersDict { guard let email = userData["email"] as? String, let username = userData["username"] as? String, let profileImageUrl = userData["profileImageUrl"] as? String else { continue } let user = User(uid: uid, userUsername: username, userProfileImageUrl: profileImageUrl, userEmail: email) users.append(user) } print("users :", users) let group = Group(groupId: groupId, groupName: groupName, users: users) self.showChatControllerForGroup(group: group) }, withCancel: { error in print("Failed to fetch users: \(error.localizedDescription)") }) }, withCancel: nil)
额外建议
- 尽量避免使用
as!强制解包,换成guard let或者if let的可选绑定,防止数据格式异常导致崩溃。 - 处理异步请求的失败回调,比如网络错误、数据缺失等情况,提升App的稳定性。
内容的提问来源于stack exchange,提问作者pozzy
相关产品推荐
相关产品推荐

