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

Swift中UITableView的cellForRowAt索引重复异常问题求助

问题描述

从Google Firestore逐个获取UserProfile用户数据,每获取一条就通过完成处理程序把用户添加到全局数组self.activeUsers,然后调用self.activeUsersTableView.reloadData()刷新列表。现在出现用户列表重叠的情况,已排除UI和自动布局问题——通过在cellForRowAt打印indexPath.row发现同一索引被重复打印,且reloadData()仅在完成处理程序这一处调用。

相关代码

TableView初始化

private lazy var activeUsersTableView: UITableView = {
    let table = UITableView()
    table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    table.dataSource = self
    table.delegate = self
    table.allowsSelection = false

    return table
}()

视图加载与布局

override func viewDidLoad() {
    super.viewDidLoad()

    loadActiveUsersTableView()
    fetchActiveUsers()
} 

func loadActiveUsersTableView() {
    self.view.addSubview(activeUsersTableView)
    self.activeUsersTableView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate(
        [
            self.activeUsersTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 150),
            self.activeUsersTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30),
            self.activeUsersTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -280),
            self.activeUsersTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -250),
        ]
    )
}

用户数据获取逻辑

func fetchActiveUsers() {
    let myCompletionHandler: (UserProfile) -> Void = { theUser in
        self.activeUsers.append(theUser)
        self.activeUsersTableView.reloadData()
    }
    self.chatsDocRef!.getDocument { document, error in
        if let error = error as NSError? {
            print("Error getting document: \(error.localizedDescription)")
        }
        else {
            if let document = document {
                let data = document.data()
                self.activeUserUIDS = data?["users"] as? [String]
                for i in 0..<(self.activeUserUIDS?.count ?? 0) {
                    print("Retrieving document for user ID: ", self.activeUserUIDS![i])
                    let userDocRef = Firestore.firestore().collection("users").document(self.activeUserUIDS![i])
                    UserProfile.getUserProfileData(userDocRef, using: myCompletionHandler)
                }
            }
        }
    }
}

Cell配置与打印输出

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    print("The row", indexPath.row)
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    return cell
}

打印输出:

The row 0
The row 0
The row 1
The row 0
The row 1
The row 2
The row 0
The row 1
The row 2
The row 3
The row 0
The row 1
The row 2
The row 3
The row 4
The row 0
The row 1
The row 2
The row 3
The row 4
The row 5
The row 0
The row 1
The row 2
The row 3
The row 4
The row 5
The row 0
The row 1
The row 2
The row 3
The row 4
The row 5
The row 0
The row 1
The row 2
The row 3
The row 4
The row 5
问题分析

核心问题在于Firestore的getDocument是异步操作,循环中逐个调用getUserProfileData时,这些请求会并行执行,完成顺序无法保证。每次有一条用户数据返回就调用reloadData(),而reloadData()会触发tableView重新加载所有可见cell,因此每新增一个用户,之前的所有行都会被重新调用cellForRowAt,导致同一索引重复打印。同时,异步请求的无序性可能导致activeUsers数组内的用户顺序与activeUserUIDS不一致,加上全量刷新时的cell复用问题,最终出现列表重叠的视觉异常。

解决方案

方案1:所有请求完成后统一刷新

用DispatchGroup等待所有用户数据请求完成,再一次性将数据加入数组并刷新tableView,避免多次全量刷新:

func fetchActiveUsers() {
    guard let chatsDocRef = self.chatsDocRef else { return }
    
    chatsDocRef.getDocument { document, error in
        if let error = error as NSError? {
            print("Error getting document: \(error.localizedDescription)")
            return
        }
        
        guard let document = document, let data = document.data(), let activeUserUIDS = data["users"] as? [String] else {
            return
        }
        
        self.activeUserUIDS = activeUserUIDS
        let dispatchGroup = DispatchGroup()
        var fetchedUsers = [UserProfile]()
        
        for uid in activeUserUIDS {
            dispatchGroup.enter()
            let userDocRef = Firestore.firestore().collection("users").document(uid)
            UserProfile.getUserProfileData(userDocRef) { user in
                fetchedUsers.append(user)
                dispatchGroup.leave()
            }
        }
        
        dispatchGroup.notify(queue: .main) {
            // 若需保持与activeUserUIDS一致的顺序,建议用字典存储后重组数组
            self.activeUsers = fetchedUsers
            self.activeUsersTableView.reloadData()
        }
    }
}

方案2:只刷新新增的行

放弃全量reloadData(),改用insertRows(at:with:)仅刷新新增的行,避免重复调用之前的cellForRowAt:

func fetchActiveUsers() {
    guard let chatsDocRef = self.chatsDocRef else { return }
    
    chatsDocRef.getDocument { document, error in
        if let error = error as NSError? {
            print("Error getting document: \(error.localizedDescription)")
            return
        }
        
        guard let document = document, let data = document.data(), let activeUserUIDS = data["users"] as? [String] else {
            return
        }
        
        self.activeUserUIDS = activeUserUIDS
        var usersDict = [String: UserProfile]()
        let totalUsers = activeUserUIDS.count
        
        for uid in activeUserUIDS {
            let userDocRef = Firestore.firestore().collection("users").document(uid)
            UserProfile.getUserProfileData(userDocRef) { user in
                usersDict[uid] = user
                
                // 所有请求完成后按顺序重组数组并刷新
                if usersDict.count == totalUsers {
                    self.activeUsers = activeUserUIDS.compactMap { usersDict[$0] }
                    self.activeUsersTableView.reloadData()
                } else {
                    // 无需严格顺序时,直接插入新增行
                    DispatchQueue.main.async {
                        self.activeUsers.append(user)
                        let newIndexPath = IndexPath(row: self.activeUsers.count - 1, section: 0)
                        self.activeUsersTableView.insertRows(at: [newIndexPath], with: .automatic)
                    }
                }
            }
        }
    }
}

方案3:串行执行请求

若必须按activeUserUIDS的顺序获取用户数据,可通过递归实现串行请求,保证顺序一致:

func fetchActiveUsers() {
    guard let chatsDocRef = self.chatsDocRef else { return }
    
    chatsDocRef.getDocument { document, error in
        if let error = error as NSError? {
            print("Error getting document: \(error.localizedDescription)")
            return
        }
        
        guard let document = document, let data = document.data(), let activeUserUIDS = data["users"] as? [String] else {
            return
        }
        
        self.activeUserUIDS = activeUserUIDS
        self.fetchUser(at: 0, from: activeUserUIDS)
    }
}

private func fetchUser(at index: Int, from uids: [String]) {
    guard index < uids.count else {
        self.activeUsersTableView.reloadData()
        return
    }
    
    let uid = uids[index]
    let userDocRef = Firestore.firestore().collection("users").document(uid)
    UserProfile.getUserProfileData(userDocRef) { user in
        self.activeUsers.append(user)
        // 可选:每新增一个就刷新,或最后统一刷新
        DispatchQueue.main.async {
            self.activeUsersTableView.reloadData()
        }
        self.fetchUser(at: index + 1, from: uids)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 07:50:23