0

很多天以来,我一直在与 tableView.performBatchUpdates 的 UIKit 问题作斗争。

我有一个简单的数据模型,其中包含由 tableView 和 tableViewCells 驱动的 15 个项目,其中包含 15 个相应的单元格(带回收),其中一个是自定义复选框控件。当单击复选框打开时,这 15 个元素中的 7 个被删除并添加(在中间),然后再次单击它以关闭,重新添加相同的 7 个单元格(在中间)。前5个不动。最后 3 个放置在中间删除/添加的 7 个元素之后。

我诊断出对数据源和表格视图的所有更新。先适当更新数据源,然后在tableView上适当调用deleteRows、insertRows和moveRow。

这在不滚动 tableView(打开和关闭状态)的情况下工作正常,导致我的 7 行显示和隐藏幻灯片 IN/OUT 动画。我可以重复多次,直到我不将单元格滚动到视野之外,它才会崩溃。

现在,如果我将 tableView 一直向上拖动,直到大多数单元格在它们上调用 cellForRow (回收并重新填充),然后使用复选框,然后我在 tableView.performBatchUpdates() 上得到 EXC_BAD_INSTRUCTION 崩溃方法(与我的任何代码无关,堆栈跟踪仅在 performBatchUpdates() 结束)。我已经对此错误进行了大量研究,通常它是 NIL 的强制解包,根据:Diagnosing EXC_BAD_INSTRUCTION in Swift standard library

问题是:我的单元格非常复杂,有很多验证和配置,堆栈跟踪不会导致我的任何代码。我还验证了Working (No scroll out of view first) vs Non working state (scroll out of view),对performBatchUpdates()中deleteRows、addRows和moveRow的逻辑没有区别。因此,随后对 cellForRow() 的调用对单元格所做的事情使 UIKit 对其内容不满意。而且由于第一次一切正常,它必须与细胞的回收有关,也许还有陈旧的数据。

有没有办法可以将 UIKit 源附加到我的项目中,这样我就可以看到 EXC_BAD_INSTRUCTION 的内容?

是否有任何其他符号调试技巧可以用来更好地理解哪些对象与 EXC_BAD_INSTRUCTION 相关?如果我从头开始制作一个示例项目,它可能会正常工作,因此它特定于我的实际单元结构和事件逻辑,这让 UIKit 感到不安。

目前,我被迫不使用 tableView 动画 (performBatchUpdates()),而只是执行 tableView.reloadData(),直到我弄清楚这一点。

使用 Exception 断点捕获异常时的图像:(堆栈跟踪仅上升到 performBatchUpdates() 本身,在 UIKit 中它内部或之上没有任何内容)。所以例外是在 UIKit 中。

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

这是正在发生的所有删除/移动/添加操作的日志:

Hide Fields: 
FORM DELETING: 0, 5
FORM DELETING: 0, 6
FORM DELETING: 0, 7
FORM DELETING: 0, 8
FORM DELETING: 0, 9
FORM DELETING: 0, 10
FORM DELETING: 0, 11
FORM MOVING: [0, 12], to: [0, 5]
FORM MOVING: [0, 14], to: [0, 7]
FORM MOVING: [0, 13], to: [0, 6]

Show Fields:
FORM ADDING: 0, 5
FORM ADDING: 0, 6
FORM ADDING: 0, 7
FORM ADDING: 0, 8
FORM ADDING: 0, 9
FORM ADDING: 0, 10
FORM ADDING: 0, 11
FORM MOVING: [0, 5], to: [0, 12]
FORM MOVING: [0, 7], to: [0, 14]
FORM MOVING: [0, 6], to: [0, 13]

// Elements 0-4 do not move.  They stay as the first 5 elements in the tableview 

这适用于任何次数的迭代的第一次尝试。此日志保持不变,随后我第一次将表单完全滚动到视图之外,然后重试显示/隐藏字段。(所有相同的日志,但因 EXC_BAD_INSTRUCTION 而崩溃)。这就是为什么我认为它与索引算法无关。

下面是 performBatchUpdates 中的 Delete、Add 和 Move 逻辑,在更新数据模型以反映新状态后调用:

tableView.deleteRows(at: toRemove, with: .none)
tableView.insertRows(at: toAdd, with: .none)

for (key, value) in toMove {
    print("FORM MOVING: \(key), to: \(value)")
    tableView.moveRow(at: key, to: value)
}
// toRemove, toAdd and toMove are populated with indexes you see above. 
4

1 回答 1

0

在深入挖掘了在 cellForRow(atIndexPath...) 中呈现我的单元格的所有逻辑之后,我发现其中一个单元格触发了一个调用链,导致基础数据模型更新而无需重新加载 tableview。(在重新加载表格视图的同时,它正在替换数据源中的一项 - 无意中)。

这解释了只有在对底层数据源进行了这种不希望的不同步更新之后,我才会在执行下一个 performBatchUpdates 操作时崩溃。(或者即使我这样做了:tableView.beginUpdates(), tableView.endUpdates() <- 同样的 EXC_BAD_INSTRUCTION 也会崩溃)。

因此,在调用 performBatchUpdates 或 .beginUpdates()/.endUpdates() 之前的数据源不同步是问题所在。一旦我删除了对数据源的意外更改,performBatchUpdates 就会运行并且动画效果很好 - 只要在它的块中,数据源更新也与 tableview 上的添加/插入/移动操作相匹配。

由于 cellForRow(...) 中的事件链复杂性,我花了很长时间才找到

于 2019-11-29T14:54:52.743 回答