我有 UITableView ,其中一些部分有一些不同的行。在我的流程中,我必须进行多次网络调用,当我收到响应时,我想更新我的表视图。问题是 - 我不知道什么时候会得到下一个响应,所以有时我会尝试更新数据源(对单元格模型进行更改),然后重新加载更新的部分/单元格,当 UITableView 已经重新加载数据时,然后应用程序崩溃。 当然,我可以使用 tableView.reloadData 安全地刷新视图,但我想知道 - 在我的情况下,是否有使用 reloadCells / insertRows / deleteRows 等方法更新 UITableView 的解决方案?
如果您要从后台线程更新表视图或其数据模型,请不要这样做。这可能会使您的应用程序崩溃。对 UI 对象(例如表视图)的所有更新都需要在主线程上进行。
如果您正在谈论更改数据模型并告诉表视图添加/删除行,请按照@PaulW11建议使用
performBatchUpdates
答案取决于崩溃是什么:
您是否因为后台线程上发生 UI 更新而崩溃?
如果是这种情况,Duncan C (+1) 是正确的,来自后台线程的更新可能会导致崩溃。我可能建议将“严格并发检查”构建设置更改为“完成”,这将有助于识别这些问题。
或者,您是否因一些类似于“由于未捕获的异常‘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
}
}