You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的空数据源触发断言:
    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")
        }
    }
    
    在Rx绑定前设置兜底数据源:
    let fallbackDataSource = FallbackTableViewDataSource()
    tableView.dataSource = fallbackDataSource
    
    // 执行Rx绑定
    viewModel.itemList.bind(to: tableView.rx.items) { ... }.disposed(by: viewModel.disposeBag)
    
    RxCocoa的数据源代理会接管UITableView的dataSource,当Rx绑定被释放时,兜底数据源会生效,避免触发断言。

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 cell
    
    不要返回空的UITableViewCell(),避免数据源与cell类型不匹配的潜在问题。

内容的提问来源于stack exchange,提问作者Mando

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:19:54