RxCocoa/RxSwift访问TableViewDataSourceNotSet时UITableView崩溃求助
问题:RxSwift/RxCocoa UITableView偶现TableViewDataSourceNotSet崩溃
我正在使用RxCocoa和RxSwift,基于BehaviorRelay提供的数组渲染UITableView,绑定数据的代码如下:
// MyViewModel: var itemList = BehaviorRelay(value: [MyItem]()) ... func loadData() { var items = [...] // 加载数据项 itemList.accept(items) } // ========== // MyView: var viewModel = MyViewModel() func bindData() { viewModel.itemList.bind(to: tableView.rx.items) { table, index, item in if item.type == ... { ... // 根据数据元信息选择单元格类型 } else { let cell = table.dequeueReusableCell(withIdentifier: "MyItemCell") as? MyItemCell cell?.bindItem(item) return cell } return 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中调用一次,每次页面出现(willAppear、从其他页面返回或应用激活/失活)时会调用loadData。崩溃来自RxCocoa/RxSwift框架设置的TableViewDataSourceNotSet,在请求单元格时崩溃,我未显式设置该自定义数据源,请问如何避免该问题?
解决方法
1. 确保绑定不会被意外释放
- 检查
viewModel.disposeBag的生命周期,避免其提前被释放(比如viewModel被意外置为nil、或者disposeBag被重置)。一旦绑定的Disposable被销毁,UITableView的数据源会被重置为TableViewDataSourceNotSet,此时UITableView请求cell就会触发断言崩溃。 - 建议将viewModel声明为不可变的
let viewModel = MyViewModel(),避免被意外置空;同时不要在非必要场景重置disposeBag。
2. 避免重复绑定或错误的数据源操作
- 确认
bindData()只在viewDidLoad中调用一次,不要在页面出现时重复绑定。重复绑定会导致之前的绑定被dispose,数据源处于不稳定状态。 - 刷新数据仅需调用
viewModel.loadData()更新BehaviorRelay的值,无需重新执行绑定逻辑。
3. 处理UITableView预加载的竞态条件
- 崩溃堆栈显示是UITableView预加载(prefetch)触发的,可能是绑定未完成或数据源释放后,UITableView仍发起cell请求。可以临时禁用预加载:
tableView.isPrefetchingEnabled = false tableView.prefetchDataSource = nil - 确保在UITableView开始渲染前,绑定逻辑已执行完成(
viewDidLoad中执行绑定通常能满足,但需避免异步延迟绑定)。
4. 设置兜底数据源代理
- 实现一个简单的UITableViewDataSource作为兜底,避免RxCocoa的空数据源触发断言:
在Rx绑定前设置兜底数据源:class FallbackTableViewDataSource: NSObject, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell(style: .default, reuseIdentifier: "FallbackCell") } }
RxCocoa的数据源代理会接管UITableView的dataSource,当Rx绑定被释放时,兜底数据源会生效,避免触发断言。let fallbackDataSource = FallbackTableViewDataSource() tableView.dataSource = fallbackDataSource // 执行Rx绑定 viewModel.itemList.bind(to: tableView.rx.items) { ... }.disposed(by: viewModel.disposeBag)
5. 确保cell复用的安全性
- 避免cell解包失败返回nil,优化cell获取逻辑:
不要返回空的guard let cell = table.dequeueReusableCell(withIdentifier: "MyItemCell") as? MyItemCell else { // 兜底创建cell,避免返回nil let cell = MyItemCell(style: .default, reuseIdentifier: "MyItemCell") cell.bindItem(item) return cell } cell.bindItem(item) return cellUITableViewCell(),避免数据源与cell类型不匹配的潜在问题。
内容的提问来源于stack exchange,提问作者Mando
相关产品推荐
相关产品推荐

