如何改进EventCenter实现订阅者销毁时自动移除事件以避免内存泄漏?
EventCenter内存泄漏问题改进方案
问题根源
- 注册事件时使用匿名lambda表达式,每次注册都会生成全新的委托实例,无法通过
DeleteEvent方法找到对应实例进行移除。 - EventCenter通过强引用持有委托,订阅者(MonoBehaviour)被销毁后,委托仍持有对象实例的引用,导致GC无法回收该对象,引发内存泄漏,且事件仍会被执行。
改进方案
方案一:订阅者手动持有委托引用,销毁时自动注销
无需大幅修改EventCenter核心逻辑,调整订阅者代码即可实现正确移除:
修改Test.cs代码
public class Test: MonoBehaviour { private UnityAction _testAction; private void Start() { // 将lambda赋值给成员变量,保存委托实例 _testAction = () => Debug.Log("Test"); EventCenter.Instance.RegisterEvent(EventType.Test, _testAction); Invoke(nameof(DestroyThis), 3f); } void DestroyThis() { Debug.Log("Destroy"); Destroy(this); } private void OnDestroy() { // 对象销毁时,移除对应的事件订阅 EventCenter.Instance.DeleteEvent(EventType.Test, _testAction); } }
优势
- 改动小,逻辑直观,适合现有项目快速修复。
- 彻底移除事件委托,避免内存泄漏和无效调用。
方案二:使用弱引用委托改造EventCenter
通过弱引用存储委托,即使订阅者被销毁,弱引用不会阻止GC回收对象,调用事件时自动过滤无效引用:
修改IEventInfo和EventInfo
public interface IEventInfo { } public class EventInfo : IEventInfo { // 存储弱引用的委托列表 private List<WeakReference<UnityAction>> _weakActions = new List<WeakReference<UnityAction>>(); public void AddAction(UnityAction action) { if (action == null) return; _weakActions.Add(new WeakReference<UnityAction>(action)); } public void Invoke() { // 遍历并移除已被回收的引用,同时执行有效委托 for (int i = _weakActions.Count - 1; i >= 0; i--) { if (_weakActions[i].TryGetTarget(out var action)) { action.Invoke(); } else { _weakActions.RemoveAt(i); } } } public void RemoveAction(UnityAction action) { if (action == null) return; for (int i = _weakActions.Count - 1; i >= 0; i--) { if (_weakActions[i].TryGetTarget(out var targetAction) && targetAction == action) { _weakActions.RemoveAt(i); break; } } } // 对外暴露有效委托数量,用于清理空事件 public int ValidActionCount => _weakActions.Count; }
修改EventCenter.cs
public class EventCenter: BaseManager<EventCenter> { private static readonly Dictionary<EventType, IEventInfo> EventDic = new Dictionary<EventType, IEventInfo>(); public void RegisterEvent(EventType eventType, UnityAction action) { if (EventDic.TryGetValue(eventType, out var info)) { if (info is EventInfo i) i.AddAction(action); } else { var eventInfo = new EventInfo(); eventInfo.AddAction(action); EventDic.Add(eventType, eventInfo); } } public void CommitEvent(EventType eventType) { if (!EventDic.TryGetValue(eventType, out var info)) return; if (info is EventInfo i) { i.Invoke(); // 执行后检查是否还有有效委托,无则移除事件类型 if (i.ValidActionCount == 0) { EventDic.Remove(eventType); } } } public void DeleteEvent(EventType eventType, UnityAction action) { if (!EventDic.TryGetValue(eventType, out var info)) return; if (info is not EventInfo i) return; i.RemoveAction(action); if (i.ValidActionCount == 0) EventDic.Remove(eventType); } }
订阅者代码(可选保留OnDestroy注销)
即使不手动注销,弱引用也会自动过滤已销毁的订阅者,但手动注销能更及时清理无效引用:
public class Test: MonoBehaviour { private UnityAction _testAction; private void Start() { _testAction = () => Debug.Log("Test"); EventCenter.Instance.RegisterEvent(EventType.Test, _testAction); Invoke(nameof(DestroyThis), 3f); } void DestroyThis() { Debug.Log("Destroy"); Destroy(this); } private void OnDestroy() { EventCenter.Instance.DeleteEvent(EventType.Test, _testAction); } }
优势
- 无需依赖订阅者手动注销,即使订阅者忘记实现
OnDestroy,也能避免内存泄漏。 - 自动清理无效引用,保持EventCenter内存高效。
方案三:结合Unity原生UnityEvent简化实现
利用Unity自带的UnityEvent,它内部已处理部分生命周期逻辑,减少自定义维护成本:
修改EventCenter.cs
public class EventCenter: BaseManager<EventCenter> { private static readonly Dictionary<EventType, UnityEvent> EventDic = new Dictionary<EventType, UnityEvent>(); public void RegisterEvent(EventType eventType, UnityAction action) { if (!EventDic.TryGetValue(eventType, out var unityEvent)) { unityEvent = new UnityEvent(); EventDic.Add(eventType, unityEvent); } unityEvent.AddListener(action); } public void CommitEvent(EventType eventType) { if (EventDic.TryGetValue(eventType, out var unityEvent)) { unityEvent.Invoke(); } } public void DeleteEvent(EventType eventType, UnityAction action) { if (EventDic.TryGetValue(eventType, out var unityEvent)) { unityEvent.RemoveListener(action); if (unityEvent.GetPersistentEventCount() == 0 && unityEvent.GetInvocationList().Length == 0) { EventDic.Remove(eventType); } } } }
订阅者代码
public class Test: MonoBehaviour { private UnityAction _testAction; private void Start() { _testAction = () => Debug.Log("Test"); EventCenter.Instance.RegisterEvent(EventType.Test, _testAction); Invoke(nameof(DestroyThis), 3f); } void DestroyThis() { Debug.Log("Destroy"); Destroy(this); } private void OnDestroy() { EventCenter.Instance.DeleteEvent(EventType.Test, _testAction); } }
优势
- 利用Unity原生API,减少自定义逻辑的维护成本。
- UnityEvent内部对委托的管理更成熟,可避免部分低级错误。
内容的提问来源于stack exchange,提问作者Wiosier
相关产品推荐
相关产品推荐

