Thread 1: EXC_BAD_ACCESS报错:TableView调用scrollToRow时崩溃
问题排查:键盘弹出时
scrollToRow引发EXC_BAD_ACCESS崩溃 可能的崩溃原因及修复方案
1. 数组越界(最常见触发场景)
当messages数组为空时,messages.count - 1会得到-1,此时创建的IndexPath(item: -1, section: 0)属于无效索引,调用scrollToRow会直接触发内存访问错误。
修复代码:
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in guard let self = self, !self.messages.isEmpty else { return } let lastIndexPath = IndexPath(item: self.messages.count - 1, section: 0) // 额外验证索引是否在tableView的有效范围内 if self.tableView.indexPathIsValid(lastIndexPath) { self.tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true) } }
给UITableView添加扩展用于验证索引有效性:
extension UITableView { func indexPathIsValid(_ indexPath: IndexPath) -> Bool { guard indexPath.section < numberOfSections else { return false } return indexPath.item < numberOfRows(inSection: indexPath.section) } }
2. 内存野指针(self/tableView已被释放)
Timer的block中直接使用self会形成强引用循环,若当前ViewController已被销毁,timer仍持有self引用,会导致访问已释放的对象(tableView也会随之释放),触发崩溃。
修复代码:
用[weak self]避免强引用,同时在block内校验对象是否存在:
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in guard let self = self, let tableView = self.tableView else { return } guard !self.messages.isEmpty else { return } let lastIndexPath = IndexPath(item: self.messages.count - 1, section: 0) if tableView.indexPathIsValid(lastIndexPath) { tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true) } }
同时在ViewController的deinit中销毁timer(需持有timer引用):
var scrollTimer: Timer? @objc func keyboardWillShow(notification: NSNotification) { // ... 原有约束处理代码 scrollTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in // ... block内容 } } deinit { scrollTimer?.invalidate() }
3. 约束更新未完成导致布局异常
修改textInputWrapperBottomConstraints后用固定时长的Timer延迟滚动不可靠——约束更新的动画可能未完成,tableView的contentSize尚未更新,滚动时会访问无效布局信息。
修复方案:
替换Timer为UIView.animate的completion回调,确保约束更新完成后再执行滚动:
@objc func keyboardWillShow(notification: NSNotification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { if textInputWrapperBottomConstraints.constant == 0 { textInputWrapperBottomConstraints.constant += keyboardSize.height UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } completion: { [weak self] _ in guard let self = self, !self.messages.isEmpty else { return } let lastIndexPath = IndexPath(item: self.messages.count - 1, section: 0) if self.tableView.indexPathIsValid(lastIndexPath) { self.tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true) } } } } }
总结
优先排查数组是否为空的情况,其次解决内存引用问题,最后用约束更新完成的回调替代固定时长Timer,提升代码可靠性。
内容的提问来源于stack exchange,提问作者Juan
相关产品推荐
相关产品推荐

