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

为同一视图控制器中两个UITableView实现numberOfRowsInSection方法及崩溃排查

解决分段控件切换TableView时的非法访问问题

这种切换分段控件后触发的非法访问(一般是EXC_BAD_ACCESS)我之前也碰到过,结合你的场景——两个TableView共用一个VC做代理/数据源,其中一个支持增删——大概率是内存管理漏洞或者数据源/代理的引用混乱导致的。下面给你几个具体的排查和修复方向:

1. 检查两个TableView的数据源数组生命周期

你说索引1的TableView支持增删行,那它的数据源数组必须是强引用的实例变量(比如var dataForTable1: [YourModel] = []),绝对不能用局部变量或者弱引用。如果切换分段时,这个数组被意外释放/置空,哪怕你切回索引0时不小心访问了它,都会触发野指针错误。

  • 务必保证两个TableView的数据源是各自独立的强引用数组,别共用同一个变量;
  • 发起fetch请求前,记得取消之前未完成的请求(比如用URLSessionTask的cancel()),避免旧请求回调时操作已经过时的数据源。

2. 别重复设置Delegate/DataSource

你既在Storyboard拖线绑定了代理,又在viewDidLoad里手动设置,这很容易导致引用混乱。建议只保留一种方式:

  • 要么全在Storyboard里拖线把VC设为两个TableView的delegate和dataSource;
  • 要么删掉Storyboard里的绑定,在viewDidLoad手动设置:
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView0.delegate = self
        tableView0.dataSource = self
        tableView1.delegate = self
        tableView1.dataSource = self
    }
    
    重点!因为同一个VC是两个TableView的共同代理,所有代理方法里一定要先判断是哪个TableView,比如:
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == tableView0 {
            return dataForTable0.count
        } else if tableView == tableView1 {
            return dataForTable1.count
        }
        return 0
    }
    
    要是这里判断错了,访问了错误的数据源数组,分分钟就会越界或者触发非法访问。

3. 确保TableView的刷新时机正确

切换分段时,别着急直接调用reloadData(),一定要等fetch请求完成、数据源数组更新后,再刷新对应的TableView:

  • 比如分段控件的回调可以这么写:
    @IBAction func segmentedControlTapped(_ sender: UISegmentedControl) {
        // 先切换TableView的显示状态
        tableView0.isHidden = (sender.selectedSegmentIndex != 0)
        tableView1.isHidden = (sender.selectedSegmentIndex != 1)
        
        // 取消另一个TableView的未完成请求,避免回调干扰
        if sender.selectedSegmentIndex == 0 {
            taskForTable1?.cancel()
            fetchDataForTable0 { [weak self] fetchedData in
                guard let self = self else { return }
                self.dataForTable0 = fetchedData
                // 一定要在主线程刷新UI
                DispatchQueue.main.async {
                    self.tableView0.reloadData()
                }
            }
        } else {
            taskForTable0?.cancel()
            fetchDataForTable1 { [weak self] fetchedData in
                guard let self = self else { return }
                self.dataForTable1 = fetchedData
                DispatchQueue.main.async {
                    self.tableView1.reloadData()
                }
            }
        }
    }
    
    这里用[weak self]是为了避免循环引用,确保VC被释放时不会被请求回调卡住。

4. 检查增删行代码的内存隐患

在索引1的TableView的增删方法里,一定要先确认操作的是正确的TableView和数据源数组:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    // 先判断是不是tableView1
    guard tableView == tableView1 else { return }
    if editingStyle == .delete {
        dataForTable1.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
    }
}

别在增删后错误刷新另一个TableView,也别修改数据源后忘记更新TableView的状态。

5. 用Zombie Objects定位具体问题

要是上面的方法都没解决,你可以开启Xcode的Zombie Objects功能,精准定位是哪个已释放的对象被访问了:

  • 打开Xcode的Edit Scheme → Run → Diagnostics,勾选Zombie Objects;
  • 重新运行APP,触发错误时,Xcode会明确告诉你是哪个对象被非法访问,这样就能直接找到问题根源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:42:08