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

多次登录后WorldMessages ViewController实例无法销毁问题求助

问题分析与解决方案

从你描述的现象和代码来看,核心问题是旧的WorldMessages ViewController实例无法被ARC回收,是多个环节的强引用和错误的UITableView更新逻辑共同导致的,我们一步步拆解:

1. 最关键的问题:通知观察者未被正确移除

你在WorldMessages VC中注册了updateWorldMessages等通知,但没有在VC销毁时移除观察者。虽然iOS 9之后NotificationCenter的默认注册方法会弱引用观察者,但当通知回调中涉及UITableView的更新操作时,tableView的动画周期可能会临时持有VC的强引用,如果此时VC已经被标记为销毁(登出时TabController被销毁),这个临时强引用就会变成“僵尸”引用,导致VC实例无法被回收。

修复方案:

在WorldMessages VC中添加deinit方法,显式移除所有相关通知的观察者:

deinit {
    NotificationCenter.default.removeObserver(self, name: .updateWorldMessages, object: nil)
    NotificationCenter.default.removeObserver(self, name: .reloadWorldMessagesTableView, object: nil)
}

2. UITableView更新逻辑存在严重错误

看你的notification_updateWorldMessages方法:你声明了tableViewDeletes和tableViewInserts数组,但完全没有往里面添加任何IndexPath,就直接调用了tableView.beginUpdates()、deleteRows、insertRows和endUpdates()。

这种空数组的无效更新操作会让UITableView进入一个异常的内部状态,它会尝试执行动画但没有实际的行变更,进而导致UIKit内部持有VC的引用不放——这就是为什么删除这四行代码后,实例残留的问题消失了。

修复方案:

正确生成删除和插入的IndexPath数组,和你对WorldMessagesStore的修改保持同步:

var count = newWorldMessagesCount
if count != 0 {
    var tableViewDeletes : [IndexPath] = []
    // 生成要删除的行的IndexPath(倒序删除,避免索引混乱)
    for i in stride(from: count - 1, to: -1, by: -1) {
        tableViewDeletes.append(IndexPath(row: i, section: 0))
        WorldMessagesStore.shared.worldMessages.remove(at: i)
    }
    
    var tableViewInserts : [IndexPath] = []
    // 生成要插入的行的IndexPath(插入到第0位)
    for _ in stride(from: count - 1, to: -1, by: -1) {
        tableViewInserts.append(IndexPath(row: 0, section: 0))
        // 按顺序插入newWorldMessages,避免内容颠倒
        WorldMessagesStore.shared.worldMessages.insert(newWorldMessages.removeLast(), at: 0)
    }
    
    // 现在执行有效更新
    tableView.beginUpdates()
    tableView.deleteRows(at: tableViewDeletes, with: .fade)
    tableView.insertRows(at: tableViewInserts, with: .fade)
    tableView.endUpdates()
}

3. 定时器的潜在泄漏风险

你的TabController中有一个每10秒执行的定时器,如果登出时没有正确销毁这个定时器,它会持有TabController的强引用,导致整个TabController(包括所有子VC)都无法被回收。

修复方案:

在TabController的deinit方法中销毁定时器:

deinit {
    // 假设你的定时器变量名为timer
    timer?.invalidate()
    timer = nil
}

为什么删除那四行代码就正常?

当你移除tableView.beginUpdates()到tableView.endUpdates()的代码后,通知回调中不再触发UITableView的更新周期,UIKit也就不会持有旧VC的引用;同时,即使旧VC的通知观察者还存在,回调中没有涉及UI组件的强引用操作,ARC就能正常回收这些旧实例。

内容的提问来源于stack exchange,提问作者Kárpáti András

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:23:24