为同一视图控制器中两个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手动设置:
重点!因为同一个VC是两个TableView的共同代理,所有代理方法里一定要先判断是哪个TableView,比如:override func viewDidLoad() { super.viewDidLoad() tableView0.delegate = self tableView0.dataSource = self tableView1.delegate = self tableView1.dataSource = self }
要是这里判断错了,访问了错误的数据源数组,分分钟就会越界或者触发非法访问。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
相关产品推荐
相关产品推荐

