iOS13专属Crash排查求助:无法复现的主线程崩溃问题
首先得说,这种“旧代码突然在新系统崩、还复现不了”的问题确实头疼——毕竟代码稳跑两年,偏偏iOS13一出来就出问题,还只在这个版本上触发。结合你给的崩溃堆栈和代码,我来捋捋最可能的原因和解决方案:
核心崩溃原因推测
从堆栈看,崩溃发生在tableSections的didSet里的self.tableView.reloadData()调用,但你已经用了DispatchQueue.main.async。最可能的问题是:
- 闭包强持有
self,导致ViewController被释放后仍被访问:在你的didSet闭包里,默认是强引用self的。如果setupData()是在ViewController已经被销毁(比如用户已经返回上一页)之后才执行,那这个异步闭包会在主队列里等待执行,等它跑起来的时候,ViewController已经被回收了,这时候访问self.tableView就会触发野指针崩溃。 - 为什么iOS13之前没出现?大概率是iOS13优化了内存回收逻辑,ViewController的释放时机更早,而iOS13之前系统对这类内存问题的容错性更高,或者GCD任务的调度时机刚好避开了释放后的访问。
针对性修复方案
1. 给闭包加上弱引用,避免野指针
这是最直接的修复,把闭包里的self改成弱引用,这样ViewController释放后闭包里的self会变成nil,调用?.reloadData()就不会执行:
private var tableSections = TableData() { didSet { DispatchQueue.main.async { [weak self] in self?.tableView.reloadData() } } }
2. 增加ViewController生命周期安全检查
如果担心弱引用还不够,可以再加一层生命周期判断,确保只有ViewController处于活跃状态时才刷新表格:
private var tableSections = TableData() { didSet { DispatchQueue.main.async { [weak self] in guard let self = self, self.isViewLoaded, !self.isBeingDismissed, !self.isMovingFromParent else { return } self.tableView.reloadData() } } }
3. 检查setupData()的调用时机
你还需要确认setupData()是不是在不恰当的时机被调用了——比如ViewController已经被dismiss/pop之后,还在后台线程里更新tableSections。可以在setupData()开头加个日志,或者在ViewController的deinit方法里打印日志,看看崩溃时ViewController是不是已经被释放了。如果是,就要调整setupData()的调用时机,确保只在ViewController活跃时执行。
额外验证建议
如果还是无法复现,可以尝试在模拟器里开启Zombie Objects(Xcode -> Edit Scheme -> Diagnostics -> 勾选Zombie Objects),这样当访问已释放对象时会立刻抛出异常,方便定位问题。
内容的提问来源于stack exchange,提问作者FredFlinstone

