已在 UITableView 重新加载期间更新 UTableView 数据源

问题描述 投票:0回答:2

我有 UITableView ,其中一些部分有一些不同的行。在我的流程中,我必须进行多次网络调用,当我收到响应时,我想更新我的表视图。问题是 - 我不知道什么时候会得到下一个响应,所以有时我会尝试更新数据源(对单元格模型进行更改),然后重新加载更新的部分/单元格,当 UITableView 已经重新加载数据时,然后应用程序崩溃。 当然,我可以使用 tableView.reloadData 安全地刷新视图,但我想知道 - 在我的情况下,是否有使用 reloadCells / insertRows / deleteRows 等方法更新 UITableView 的解决方案?

ios swift uitableview uikit datasource
2个回答
2
投票

如果您要从后台线程更新表视图或其数据模型,请不要这样做。这可能会使您的应用程序崩溃。对 UI 对象(例如表视图)的所有更新都需要在主线程上进行。

如果您正在谈论更改数据模型并告诉表视图添加/删除行,请按照@PaulW11建议使用

performBatchUpdates


0
投票

答案取决于崩溃是什么:

  1. 您是否因为后台线程上发生 UI 更新而崩溃?

    如果是这种情况,Duncan C (+1) 是正确的,来自后台线程的更新可能会导致崩溃。我可能建议将“严格并发检查”构建设置更改为“完成”,这将有助于识别这些问题。

  2. 或者,您是否因一些类似于“由于未捕获的异常‘NSInternalInconsistencyException’而终止应用程序”的消息而崩溃?

    但是,当获取大量更新时,手动更新、插入、删除和移动行可能会很脆弱。在处理多个网络请求、用户交互等的组合时,这些很容易失去同步。是的,您可以通过重新加载整个表视图来解决这个问题,但这并不像只更新表视图那么优雅。相关行/部分。

    一种解决方案是使用“可变化”数据源,

    UITableViewDiffableDataSource
    ,如 WWDC 2019 的UI 数据源的进展视频中所述:

    class ViewController: UIViewController {    
        private var dataSource: UITableViewDiffableDataSource<Section, Item>?
        private var items: [Item] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            dataSource = .init(tableView: tableView) { tableView, indexPath, item in
                let cell = tableView.dequeueReusableCell(withIdentifier: …, for: indexPath)
                cell.textLabel?.text = item.string
                return cell
            }
            tableView.dataSource = dataSource
        }
    }
    

    注意,不需要

    UITableViewDataSource
    方法。没有“部分数量”,没有“项目数量”等。您只需创建这个可比较的数据源并将其附加到您的表中。

    每当您更新模型时,您都会

    apply
    您的更改:

    class ViewController {
        …
    
        func updateDataSource(animated: Bool = true) {
            var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
            snapshot.appendSections([.main])
            snapshot.appendItems(items)
            dataSource?.apply(snapshot, animatingDifferences: animated)
        }
    }
    

    操作系统将找出哪些行需要更新、删除、插入、移动等。


这是获取大量不同更新时使用的 diffable 数据源的更完整演示:

struct Item: Identifiable, Hashable {
    let id = UUID()
    var string: String
}

class ViewController: UITableViewController {    
    private var dataSource: UITableViewDiffableDataSource<Section, Item>?
    private var items: [Item] = []
    private var tasks: [Task<Void,Error>] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureTableView()        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        startUpdatingModel()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        for task in tasks {
            task.cancel()
        }
        tasks = []
    }
}

// MARK: - Private methods

extension ViewController {    
    func configureTableView() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        dataSource = .init(tableView: tableView) { tableView, indexPath, item in
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = item.string
            return cell
        }
        dataSource?.defaultRowAnimation = .fade
        tableView.dataSource = dataSource
    }
    
    // diffable data source handles insertions, deletions, mutations, and reordering
    
    func startUpdatingModel() {
        // start inserting items slowly
        
        let insertTask = Task {
            for i in 0 ..< 10 {
                items.append(Item(string: "\(i)"))
                updateDataSource()
                try await Task.sleep(for: .seconds(2))
            }
        }
        
        // randomly change text of given item
        
        let mutateTask = Task {
            for i in 0 ..< 100 {
                if let index = items.indices.randomElement() {
                    items[index].string += "."
                    updateDataSource()
                }
                try await Task.sleep(for: .seconds(0.5))
            }
        }
        
        // move random item to start of the array
        
        let moveTask = Task {
            for i in 0 ..< 10 {
                if let index = items.indices.dropFirst().randomElement() {
                    items.move(fromOffsets: [index], toOffset: 0)
                    updateDataSource()
                }
                try await Task.sleep(for: .seconds(5))
            }
        }
        
        tasks = [insertTask, mutateTask, moveTask]
    }
    
    func updateDataSource(animated: Bool = true) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items)
        dataSource?.apply(snapshot, animatingDifferences: animated)
    }
}

extension ViewController {
    enum Section {
        case main
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.