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

如何改进EventCenter实现订阅者销毁时自动移除事件以避免内存泄漏?

EventCenter内存泄漏问题改进方案

问题根源

  1. 注册事件时使用匿名lambda表达式,每次注册都会生成全新的委托实例,无法通过DeleteEvent方法找到对应实例进行移除。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:43:09