很多天以来,我一直在与 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.