我正在使用 RxCocoa 和 RxSwift 根据
UITableView
提供的数组渲染 BehaviorRelay
。绑定数据的代码如下:
// MyViewModel:
var itemList = BehaviorRelay(value: [MyItem]())
...
func loadData() {
var items = [...] // load items
itemList.accept(items)
}
// ==========
// MyView:
var viewModel = MyViewModel()
func bindData() {
viewModel.itemList.bind(to: tableView.rx.items) { table, index, item in
if item.type == ... {
... // pick cell type based on the item metadata
} else {
let cell = table.dequeueReusableCell(withIdentifier: "MyItemCell") as? MyItemCell {
cell.bindItem(item)
return cell
}
return new UITableViewCell()
}).disposed(by: viewModel.disposeBag)
}
// ==========
在大多数情况下,它工作得很好,但在极少数情况下,我无法使用以下堆栈跟踪重现应用程序崩溃,一些 Firebase 用户报告说:
Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x37d7c _assertionFailure(_:_:file:line:flags:) + 312
1 mycoolapp 0x8aa968 @objc TableViewDataSourceNotSet.tableView(_:cellForRowAt:) + 84 (RxCocoa.swift:84)
2 mycoolapp 0x8aae30 @objc RxTableViewDataSourceProxy.tableView(_:cellForRowAt:) + 64 (RxTableViewDataSourceProxy.swift:64)
3 UIKitCore 0x14b75c -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 808
4 UIKitCore 0x219c9c -[UITableView _createPreparedCellForRowAtIndexPath:willDisplay:] + 68
5 UIKitCore 0x219884 -[UITableView _heightForRowAtIndexPath:] + 124
6 UIKitCore 0x219720 -[UISectionRowData heightForRow:inSection:canGuess:] + 176
7 UIKitCore 0x44c328 -[UITableViewRowData heightForRow:inSection:canGuess:adjustForReorderedRow:] + 228
8 UIKitCore 0x5952c -[UITableViewRowData rectForRow:inSection:heightCanBeGuessed:] + 304
9 UIKitCore 0x44b8b0 -[UITableViewRowData rectForGlobalRow:heightCanBeGuessed:] + 112
10 UIKitCore 0x14b378 -[UITableView _prefetchCellAtGlobalRow:aboveVisibleRange:] + 240
11 UIKitCore 0x14b264 __48-[UITableView _configureCellPrefetchingHandlers]_block_invoke + 52
12 UIKitCore 0x2186a8 -[_UITableViewPrefetchContext updateVisibleIndexRange:withContentOffset:] + 2100
13 UIKitCore 0x81be8 -[UITableView _updateCycleIdleUntil:] + 168
14 UIKitCore 0x811ac ___UIUpdateCycleNotifyIdle_block_invoke + 612
15 libdispatch.dylib 0x2460 _dispatch_call_block_and_release + 32
16 libdispatch.dylib 0x3f88 _dispatch_client_callout + 20
17 libdispatch.dylib 0x127f4 _dispatch_main_queue_drain + 928
18 libdispatch.dylib 0x12444 _dispatch_main_queue_callback_4CF + 44
19 CoreFoundation 0x9a6c8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
20 CoreFoundation 0x7c02c __CFRunLoopRun + 2036
21 CoreFoundation 0x80eb0 CFRunLoopRunSpecific + 612
22 GraphicsServices 0x1368 GSEventRunModal + 164
23 UIKitCore 0x3a1668 -[UIApplication _run] + 888
24 UIKitCore 0x3a12cc UIApplicationMain + 340
25 mycoolapp. 0x1d8564 main + 23 (AppDelegate.swift:23)
26 ??? 0x1b3d9c960 (Missing)
崩溃很难重现(我没能重现),但它会影响多个用户。
bindData
方法在 viewDidLoad
被调用一次,我在每个页面出现时调用 loadData
(willAppear,从另一个页面返回或应用程序激活停用)但不清楚它为什么崩溃以及如何重现/修复它.
崩溃来自
TableViewDataSourceNotSet
,它似乎是由 RxCocoa/RxSwift 框架设置的,并且在请求项目的单元格时崩溃。我没有在任何地方明确设置自定义数据源。有什么想法可以防止这种情况发生吗?
bindData 方法在每个页面出现 (willAppear) 时被调用,但不清楚它崩溃的原因以及如何重现/修复它。
以上是红旗。您不应该对每个页面外观都具有约束力。导致解除绑定然后重新绑定的最佳情况。这可能是在表视图从先前安装的数据源调用
numberOfRowsInSection
,然后在 cellForRowAt
对象上调用 TableViewDataSourceNotSet
的某些地方使系统跳闸。
你的绑定应该只发生一次。通常在 viewDidLoad 中。
根据堆栈跟踪,当尝试从表视图中出列单元格时,崩溃似乎发生在 RxSwift 库中。错误信息“TableViewDataSourceNotSet”表示RxSwift没有正确设置表格视图的数据源。
这里有一些防止崩溃的建议:
请确保您已使用 RxSwift 正确设置表视图的数据源。在您的 bindData() 方法中,确保您已使用 bind(to:tableView.rx.items) 方法正确设置数据源。仔细检查您是否为此方法提供了正确的参数,包括表视图和返回单元格的闭包。
确保您正确处理 RxSwift 订阅以避免任何内存泄漏。在您的 bindData() 方法中,您使用 disposed(by: viewModel.disposeBag) 来处理订阅。确保您正在为视图模型使用适当的处理包,并且当视图被关闭或不再需要时它被正确释放。
检查 itemList BehaviorRelay 的任何竞争条件或并发更新。如果同时从多个线程或队列更新 itemList,可能会导致意外行为和崩溃。确保仅从主线程更新 itemList BehaviorRelay,并考虑使用适当的同步机制,如 DispatchQueue 或 NSLock 来防止竞争条件。
考虑在 RxSwift 中使用 catchError 运算符来优雅地处理任何错误或意外情况。您可以在绑定闭包中添加一个 catchError 运算符来捕获任何错误并适当地处理它们,例如通过显示错误消息或记录错误信息以进行调试。
尝试通过创建测试用例或使用断点和日志记录等调试技术来在本地重现崩溃,以确定可能导致崩溃的任何特定场景或边缘情况。一旦您可以重现崩溃,调查和解决问题就会更加容易。
如果问题仍然存在,查看 RxSwift 文档并检查与表视图绑定相关的任何已知问题或更新可能会有所帮助。此外,考虑从 RxSwift 社区寻求帮助或在 RxSwift GitHub 存储库上提出问题以获得进一步的帮助。