Firebase查询重复渲染数据:实现初始加载+新增单条渲染需求
解决Firebase实时数据库重复加载帖子的问题
你的推测完全正确!observe(.value)的特性就是每次节点数据发生任何变化(新增、修改、删除),都会返回该节点的完整数据快照,而你当前的逻辑是把这些全量数据直接追加到postList数组里,旧数据没被清空,自然就会出现重复单元格的问题——新增时既拿到全量数据又追加,删除后返回的新全量数据也还是会被加在旧数组后面,直到退出视图重新加载才清空数组。
下面给你两种解决方案,推荐第一种,更符合Firebase实时数据库的设计思路:
方案一:使用精准的事件类型(推荐)
Firebase提供了childAdded、childRemoved等细粒度的事件类型,分别对应单个节点的新增和删除,这样我们只需要处理变化的单个数据,不会重复加载全量内容:
步骤1:给Post模型添加id属性
首先需要在你的Post类里添加一个id属性,用来存储Firebase节点的唯一key,这样删除时能精准找到对应的帖子:
class Post { var post_words: String? var poster: String? var Reveals: String? var id: String? // 新增这个属性 }
步骤2:修改fetchPosts函数
func fetchPosts () { // 先清空数组,避免函数重复调用时出现重复数据 postList.removeAll() tableView.reloadData() let query = ref.queryOrdered(byChild: "timestamp").queryLimited(toFirst: 10) // 处理初始加载和新增的帖子:childAdded会按查询顺序返回所有现有数据,之后新增一条就触发一次 query.observe(.childAdded) { (snapshot) in guard let value = snapshot.value as? NSDictionary else { return } let post = Post() post.id = snapshot.key // 存储节点key post.poster = value["poster"] as? String ?? "Name not found" post.post_words = value["post"] as? String ?? "Content not found" post.Reveals = value["Reveals"] as? String ?? "Reveals not found" postList.append(post) DispatchQueue.main.async { self.tableView.reloadData() } } // 处理帖子删除事件:当有帖子被删除时,触发这个事件 query.observe(.childRemoved) { (snapshot) in // 根据节点key找到数组中对应的帖子并移除 postList.removeAll { $0.id == snapshot.key } DispatchQueue.main.async { self.tableView.reloadData() } } }
步骤3:移除观察者(避免内存泄漏)
在视图消失时移除所有Firebase观察者,防止离开页面后还收到事件:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) ref.removeAllObservers() // 如果query是类的成员变量,也可以用query.removeAllObservers()来精准移除 }
这种方案的优势是性能更好,每次只处理单个数据变化,不需要刷新整个数据集,也不会出现重复问题。
方案二:保留observe(.value)但清空数组
如果你暂时不想修改事件类型,也可以在处理全量快照前先清空数组,这样每次都会重新加载最新的全量数据,不会有重复:
func fetchPosts () { let query = ref.queryOrdered(byChild: "timestamp").queryLimited(toFirst: 10) query.observe(.value) { (snapshot) in // 关键:先清空数组,再重新添加所有数据 self.postList.removeAll() for child in snapshot.children.allObjects as! [DataSnapshot] { guard let value = child.value as? NSDictionary else { continue } let post = Post() post.id = child.key // 同样建议添加id属性,方便后续处理删除 post.poster = value["poster"] as? String ?? "Name not found" post.post_words = value["post"] as? String ?? "Content not found" post.Reveals = value["Reveals"] as? String ?? "Reveals not found" self.postList.append(post) } // 循环结束后统一刷新表格,不要在循环内刷新(性能差) DispatchQueue.main.async { self.tableView.reloadData() } } }
这种方案的缺点是每次数据变化都要重新加载全部数据,刷新整个表格,数据量大的时候会有明显的性能问题,所以还是更推荐第一种方案。
内容的提问来源于stack exchange,提问作者Raim Khalil
相关产品推荐
相关产品推荐

