Unity中订阅事件的追踪、性能分析与内存泄漏检测问题
问题与代码
private void Start() { _winPanelController.OnGiveAwards += CalculateCoins; _revivePaymentManager.OnUpdatedBalance += SaveBalanceToJSON; _revivePaymentManager.OnPayingFinished += RespawnAllEnemies; _revivePaymentManager.OnPayingFinished += RevivePlayer; _revivePaymentManager.OnPayingNotFinished += InformPlayerNotEnoughGems; _actorUI.OnPlayerStop += SetPlayerMovement; _wavesController.OnWin += ActivationWinPanel; _wavesController.OnStoppedWave += StopSpawningForAllSpawners; _wavesController.OnEnemyLevelUp += SetNewLevelForEnemies; _player.OnDead += EnableRevivePanel; _player.OnRevived += RespawnAllEnemies; foreach (var spawner in _enemySpawner) { spawner.OnXPCollected += CalculateCollectedXP; spawner.OnDamageCollected += CalculateCollectedDamage; } } private void OnDestroy() { _winPanelController.OnGiveAwards -= CalculateCoins; _revivePaymentManager.OnUpdatedBalance -= SaveBalanceToJSON; _revivePaymentManager.OnPayingFinished -= RespawnAllEnemies; _revivePaymentManager.OnPayingFinished -= RevivePlayer; _revivePaymentManager.OnPayingNotFinished -= InformPlayerNotEnoughGems; _wavesController.OnWin -= ActivationWinPanel; _wavesController.OnStoppedWave -= StopSpawningForAllSpawners; _wavesController.OnEnemyLevelUp -= SetNewLevelForEnemies; _player.OnDead -= EnableRevivePanel; _player.OnRevived -= RespawnAllEnemies; foreach (var spawner in _enemySpawner) { spawner.OnXPCollected -= CalculateCollectedXP; spawner.OnDamageCollected -= CalculateCollectedDamage; } }
我的疑问是:我该如何追踪已订阅事件的数量、它们占用的RAM或CPU时长?或者是否无法进行此类追踪?如果我不取消这些订阅会发生什么,该如何检测由此产生的内存泄漏?
我曾尝试在Memory Profiler中查找与事件相关的内容,但一无所获。我在Profiler中找到的最相关内容是进程Update.ScriptsRunBehaviorUpdate > BehaviorUpdate > EventSystem.Update() [Invoke]。这是Unity的标准EventSystem吗?
解答
一、事件订阅的追踪方法
1. 追踪订阅数量
- 针对自定义事件,可自行封装事件容器类,在
+=和-=操作时维护订阅计数。比如用字典记录每个事件的订阅者数量,或给事件添加SubscriberCount属性实时更新。 - 若使用Unity自带的
UnityEvent,可通过反射访问其内部的m_Calls列表获取订阅数,但反射有性能开销,仅适合调试场景。
2. RAM与CPU开销追踪
- RAM占用:单个事件订阅本质是委托引用,内存开销仅为指针大小,几乎可忽略。要追踪的话,在Unity Memory Profiler的Managed Objects视图中筛选
Delegate类型,查看相关实例的数量与内存占比,仅当订阅量极大时才会有明显影响。 - CPU时长:在Unity Profiler的CPU Usage视图中,直接查看事件回调函数(如
CalculateCoins、SaveBalanceToJSON)的耗时。若要统计事件触发的总耗时,可添加自定义Profiler标记:
即可在Profiler中看到该事件触发的总耗时。// 在事件触发处添加 Profiler.BeginSample("OnGiveAwards Trigger"); OnGiveAwards?.Invoke(); Profiler.EndSample();
二、不取消订阅的后果
- 内存泄漏:若订阅者所在GameObject被销毁,但事件发布者(如
_winPanelController)仍存活,委托会持有订阅者对象的引用,导致GC无法回收该对象,引发内存泄漏。 - 无效回调执行:发布者后续触发事件时,已销毁订阅者的回调仍会被执行,大概率引发空引用异常或逻辑错误(如访问已销毁对象的组件)。
三、内存泄漏的检测方法
- Unity Memory Profiler:
- 场景初始化时拍摄内存快照,销毁订阅者对象后触发
GC.Collect(),再拍摄第二个快照。对比两个快照,查找本该被销毁却残留的脚本实例。 - 查看残留对象的引用链:在Memory Profiler中选中残留实例,查看其引用来源,若指向某事件委托,即可确认是未取消订阅导致的泄漏。
- 场景初始化时拍摄内存快照,销毁订阅者对象后触发
- 自定义检测逻辑:
- 在脚本中用列表记录所有订阅的委托,
OnDestroy时逐一校验是否已移除;或在调试模式下,打印未取消的订阅信息。
- 在脚本中用列表记录所有订阅的委托,
四、关于Profiler中的EventSystem
你看到的Update.ScriptsRunBehaviorUpdate > BehaviorUpdate > EventSystem.Update() [Invoke]是Unity标准的UI EventSystem,负责处理按钮点击、拖拽等UI交互事件,和你代码中的自定义事件系统无关。你的自定义事件触发不会出现在这个调用栈中,而是会直接显示在对应回调函数的调用栈里。
内容的提问来源于stack exchange,提问作者Dmytro Demianii
相关产品推荐
相关产品推荐

