Firebase实时数据库:queryLimited下.childAdded触发重复数据问题
问题分析与解决方案
咱们先来拆解你遇到的核心问题:
你现在有两个独立的Firebase Realtime Database监听逻辑:
addChatDataListener:仅监听最新3条聊天记录的childAdded事件,用来初始化客户端的聊天数组addChatChangeListener:监听所有聊天记录的childMoved事件,处理数据位置变更
重复数据的根源就在于第二个监听没有加上和第一个一致的queryLimited(toLast: 3)限制:
- 当你修改未在初始数组里的chat4的timestamp,使其进入最新3条范围时,
addChatDataListener的查询会触发childAdded,把chat4插入到数组中 - 同时,全局范围的
childMoved监听也会被触发,此时数组里还没有chat4,indexOfChat返回-1,代码就会再次插入chat4,导致重复 - 而修改本来就在前3范围内的chat3时,只会触发
childMoved,不会有重复问题
修复方案
1. 统一两个监听的查询范围
给addChatChangeListener也加上queryLimited(toLast: 3)限制,让它只关注最新3条数据的变更,这样就不会处理超出范围的记录:
func addChatChangeListener(){ // 加上和初始化监听一致的查询限制 ref.child("user_chats").child(myUsername) .queryOrdered(byChild: "timestamp") .queryLimited(toLast: 3) // 新增这一行 .observe(.childMoved, with: {(snapshot) in let index = self.indexOfChat(key: snapshot.key) let chat = self.getChatObject(snapshot: snapshot) print("child moved: \(index)") print("child moved: \(chat)") if(index == -1){ // 加了查询限制后,这个分支理论上不会再触发,保留容错逻辑即可 self.chats.insert(chat, at: 0) self.tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) }else{ self.chats[index] = chat let updatedChat = self.chats.remove(at: index) self.chats.insert(updatedChat, at: 0) self.tableView.moveRow(at: IndexPath(row: index, section: 0), to: IndexPath(row: 0, section: 0)) } }) }
2. 合并监听逻辑(更优方案)
把初始化和变更监听合并到同一个查询里,逻辑更统一,也能从根源避免冲突:
func setupChatListeners() { // 定义统一的查询规则 let chatQuery = ref.child("user_chats").child(myUsername) .queryOrdered(byChild: "timestamp") .queryLimited(toLast: 3) // 处理初始数据和新增数据 chatQuery.observe(.childAdded) { snapshot in let chat = self.getChatObject(snapshot: snapshot) print("child added: \(chat)") // 先检查是否已存在,彻底杜绝重复 if !self.chats.contains(where: { $0.key == snapshot.key }) { self.chats.insert(chat, at: 0) self.tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) } } // 处理位置变更 chatQuery.observe(.childMoved) { snapshot in let chatKey = snapshot.key guard let oldIndex = self.chats.firstIndex(where: { $0.key == chatKey }) else { // 如果不在数组里,说明是刚进入前3的记录,交给childAdded处理即可 return } let updatedChat = self.getChatObject(snapshot: snapshot) // 更新数据并调整位置 self.chats[oldIndex] = updatedChat let movedChat = self.chats.remove(at: oldIndex) self.chats.insert(movedChat, at: 0) self.tableView.moveRow(at: IndexPath(row: oldIndex, section: 0), to: IndexPath(row: 0, section: 0)) } }
然后在viewDidLoad里替换原来的两个方法调用:
override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self tableView.frame = self.view.frame tableView.register(ChatTableViewCell.self, forCellReuseIdentifier: "chatCell") self.view.addSubview(tableView) ref = Database.database().reference() setupChatListeners() // 替换原来的两个监听方法 }
为什么这样有效?
- 统一查询范围后,所有监听都只关注最新3条数据,不会处理超出范围的记录变更
- 合并监听逻辑避免了两个独立查询可能带来的事件冲突
- 新增的存在性检查进一步杜绝了重复插入的可能
内容的提问来源于stack exchange,提问作者j.doe
相关产品推荐
相关产品推荐

