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

Firebase填充数组重复行:聊天App会话列表重复问题求助

问题根源与修复方案

核心问题

  1. 重复注册监听器:每次调用fetchRooms都会新增Firestore快照监听器,同一条消息变更会被多个监听器处理,重复添加到rooms数组。
  2. 未处理变更类型:直接将所有documentChanges追加到数组,没有针对会话更新(比如用最新消息替换旧消息)做去重或替换逻辑。
  3. 异步加载导致重复追加:UserService.fetchUser是异步操作,多次触发时会重复创建Room实例并追加,未检查数组中是否已存在该会话。

修复步骤

1. 移除重复监听器

不要每次调用fetchRooms都新增监听器,改为初始化时注册一次,或在注册前移除旧监听器。可以保存监听器引用,在页面销毁时清理:

private var roomsListener: ListenerRegistration?

func setupRoomsListener() {
    // 先移除旧监听器,避免重复监听
    roomsListener?.remove()
    
    let query = COLLECTION_ROOMS.order(by: "timestamp", descending: false).limit(toLast: 10)
    roomsListener = query.addSnapshotListener { [weak self] snapshot, err in
        guard let self = self else { return }
        // 后续变更处理逻辑
    }
}

2. 按会话唯一标识去重,更新而非追加

每个会话应有唯一标识(比如用户UID或房间ID),处理变更时先检查数组中是否已有该会话,存在则更新最新消息,不存在则添加:

snapshot?.documentChanges.forEach({ [weak self] change in
    guard let self = self else { return }
    let dictionary = change.document.data()
    let message = RoomMessage(dictionary: dictionary)
    let ownerID = dictionary["ownerID"] as? String ?? ""
    
    UserService.fetchUser(withUid: ownerID) { user in
        let conversation = Room(user: user, recentMessage: message)
        // 按用户UID判断会话是否已存在
        if let index = self.rooms.firstIndex(where: { $0.user.uid == user.uid }) {
            // 存在则更新最新消息
            self.rooms[index].recentMessage = message
        } else {
            // 不存在则添加新会话
            self.rooms.append(conversation)
        }
        // 排序后统一刷新UI,避免频繁刷新
        self.rooms.sort(by: { $0.recentMessage.timeStamp.compare($1.recentMessage.timeStamp) == .orderedDescending })
        self.tableView.reloadData()
    }
})

3. 拆分分页与实时监听逻辑

分页加载历史数据用getDocuments(不需要实时监听),实时监听只用于最新消息的更新:

@objc func fetchRooms() {
    tableView.refreshControl?.beginRefreshing()
    var query: Query!

    if rooms.isEmpty {
        query = COLLECTION_ROOMS.order(by: "timestamp", descending: false).limit(toLast: 10)
        print("First 10 room loaded")
    } else {
        query = COLLECTION_ROOMS.order(by: "timestamp", descending: false).end(beforeDocument: lastDocumentSnapshot).limit(toLast: 10)
        print("Next 10 room loaded")
        currentPage += 1
    }

    // 用getDocuments加载历史分页数据,而非添加监听器
    query.getDocuments { [weak self] snapshot, err in
        guard let self = self else { return }
        self.tableView.refreshControl?.endRefreshing()
        if let err = err {
            print("\(err.localizedDescription)")
            return
        }
        guard let snapshot = snapshot, !snapshot.isEmpty else { return }
        self.lastDocumentSnapshot = snapshot.documents.first
        
        for document in snapshot.documents {
            let dictionary = document.data()
            let message = RoomMessage(dictionary: dictionary)
            let ownerID = dictionary["ownerID"] as? String ?? ""
            
            UserService.fetchUser(withUid: ownerID) { user in
                let conversation = Room(user: user, recentMessage: message)
                // 去重检查
                if !self.rooms.contains(where: { $0.user.uid == user.uid }) {
                    self.rooms.append(conversation)
                    self.rooms.sort(by: { $0.recentMessage.timeStamp.compare($1.recentMessage.timeStamp) == .orderedDescending })
                    self.tableView.reloadData()
                }
            }
        }
    }
}

4. 优化刷新逻辑

刷新时重置分页状态,清空数组后重新拉取数据:

@objc func refresh() {
    self.rooms.removeAll()
    self.currentPage = 1
    self.lastDocumentSnapshot = nil
    self.setupRoomsListener()
    self.tableView.reloadData()
}

内容的提问来源于stack exchange,提问作者Unknown

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 22:05:27