Firebase Swift聊天应用:如何获取每个联系人的最后一条消息
解决Swift Firebase聊天应用展示联系人最后一条消息的问题
嘿,我来帮你搞定这个需求!要在首页菜单显示每个联系人的最后一条消息,核心思路要么是维护一个专门存储最后消息的节点(推荐,性能更好),要么是直接查询每个联系人的消息记录取最后一条。下面给你详细的实现方案:
方案一:维护单独的「最后消息」节点(推荐)
这种方式是在发送消息时,同步更新一个专门的lastMessages节点,这样首页读取时直接拉取这个节点的数据,效率很高,不用反复查询完整的消息历史。
步骤1:定义数据模型
先创建一个结构体来存储联系人信息和最后消息:
struct ChatUser { let userId: String let name: String? let lastMessage: String? let lastMessageTimestamp: TimeInterval? }
步骤2:实现getAllMsg()函数
这个函数会读取当前用户的lastMessages节点,结合用户信息组装成列表:
func getAllMsg() { self.users = [] guard let currentUserId = Auth.auth().currentUser?.uid else { print("用户未登录") return } // 指向当前用户的最后消息节点 let lastMessagesRef = Database.database().reference().child("lastMessages").child(currentUserId) // 监听节点变化,实时更新列表 lastMessagesRef.observe(.value) { snapshot in guard let lastMessagesDict = snapshot.value as? [String: [String: Any]] else { self.users = [] self.tableView.reloadData() return } // 清空旧数据,避免重复添加 self.users.removeAll() for (recipientId, messageData) in lastMessagesDict { let lastMessage = messageData["content"] as? String ?? "" let timestamp = messageData["timestamp"] as? TimeInterval ?? 0 // 获取联系人的基本信息(比如昵称) let userRef = Database.database().reference().child("users").child(recipientId) userRef.observeSingleEvent(of: .value) { userSnapshot in guard let userData = userSnapshot.value as? [String: Any] else { return } let userName = userData["name"] as? String ?? "未知用户" let chatUser = ChatUser( userId: recipientId, name: userName, lastMessage: lastMessage, lastMessageTimestamp: timestamp ) self.users.append(chatUser) // 按最后消息时间排序,最新的对话排在最前面 self.users.sort { $0.lastMessageTimestamp ?? 0 > $1.lastMessageTimestamp ?? 0 } self.tableView.reloadData() } } } }
步骤3:发送消息时同步更新最后消息节点
在你发送消息的函数里,加上更新lastMessages的逻辑,确保两端用户的最后消息都同步:
func sendMessage(to recipientId: String, content: String) { guard let currentUserId = Auth.auth().currentUser?.uid else { return } let timestamp = Date().timeIntervalSince1970 // 组装消息数据 let messageData = [ "content": content, "senderId": currentUserId, "timestamp": timestamp ] as [String: Any] // 更新当前用户的最后消息记录 Database.database().reference() .child("lastMessages") .child(currentUserId) .child(recipientId) .setValue(messageData) // 更新收件人的最后消息记录 Database.database().reference() .child("lastMessages") .child(recipientId) .child(currentUserId) .setValue(messageData) // 同时把消息存入完整的聊天历史节点(可选,保留所有对话记录) let messageHistoryRef = Database.database().reference() .child("messages") .child(currentUserId) .child(recipientId) .childByAutoId() messageHistoryRef.setValue(messageData) }
方案二:直接查询每个联系人的最后一条消息
如果不想维护额外节点,也可以直接查询每个联系人的消息历史,取时间戳最新的那一条。但注意:如果联系人数量多,这种方式会发起大量查询,性能较差,仅适合联系人较少的场景。
实现代码
func getAllMsg() { self.users = [] guard let currentUserId = Auth.auth().currentUser?.uid else { return } // 先获取当前用户的所有联系人(假设你有一个contacts节点存储联系人ID) let contactsRef = Database.database().reference().child("contacts").child(currentUserId) contactsRef.observe(.value) { snapshot in guard let contactIds = snapshot.value as? [String: Bool] else { self.users = [] self.tableView.reloadData() return } self.users.removeAll() for recipientId in contactIds.keys { // 查询该联系人的最后一条消息:按时间戳倒序,取第一条 let messagesRef = Database.database().reference() .child("messages") .child(currentUserId) .child(recipientId) .queryOrdered(byChild: "timestamp") .queryLimited(toLast: 1) messagesRef.observeSingleEvent(of: .value) { messageSnapshot in var lastMessage: String? = nil var timestamp: TimeInterval = 0 if let messageDict = messageSnapshot.children.allObjects.first as? DataSnapshot, let messageData = messageDict.value as? [String: Any] { lastMessage = messageData["content"] as? String timestamp = messageData["timestamp"] as? TimeInterval ?? 0 } else { // 没有消息的情况 lastMessage = "暂无消息" } // 获取联系人信息并添加到列表 self.fetchUserInfoAndAdd(recipientId: recipientId, lastMessage: lastMessage, timestamp: timestamp) } } } } // 封装获取用户信息的逻辑,避免重复代码 func fetchUserInfoAndAdd(recipientId: String, lastMessage: String?, timestamp: TimeInterval) { let userRef = Database.database().reference().child("users").child(recipientId) userRef.observeSingleEvent(of: .value) { userSnapshot in guard let userData = userSnapshot.value as? [String: Any] else { return } let userName = userData["name"] as? String ?? "未知用户" let chatUser = ChatUser( userId: recipientId, name: userName, lastMessage: lastMessage, lastMessageTimestamp: timestamp ) self.users.append(chatUser) self.users.sort { $0.lastMessageTimestamp ?? 0 > $1.lastMessageTimestamp ?? 0 } self.tableView.reloadData() } }
额外提示
- 记得在
viewWillDisappear里移除Firebase监听,避免内存泄漏:比如lastMessagesRef.removeAllObservers() - 如果使用Firestore而非Realtime Database,思路类似:可以维护一个
lastMessages集合,或者用orderBy("timestamp", descending: true).limit(to: 1)查询最后一条消息 - 排序逻辑很重要,让最新的对话始终排在列表最上方
内容的提问来源于stack exchange,提问作者William Loke
相关产品推荐
相关产品推荐

