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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:01:17