うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】なぜ UnityEvent は RemoveAllListeners しないと開放されないのか

【Unity】UnityEvent で 利用した Delegate は初期化しないとリークする - うにてぃブログ

昨日の記事でリークすることが判明しましたが、なぜリークするのか内部処理を追ってみます

※ Unity2020.3.14f1 UnityEditor上 Memory Profilerで確認

UnityEvent

UnityEvent.csのコードにて AddListener を行った処理は以下になります

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
[Serializable]
public class UnityEvent : UnityEventBase
{
        public void AddListener(UnityAction call)
        {
            AddCall(GetDelegate(call));
        }
}
 
public abstract class UnityEventBase : ISerializationCallbackReceiver
{
        private InvokableCallList m_Calls;
  
        internal void AddCall(BaseInvokableCall call)
        {
            m_Calls.AddListener(call);
        }
}

同じく UnityEvent.cs 内にある Addlistener を見てみると m_RuntimeCalls に追加されており、クリアされているのは Clear を呼び出したタイミングになります

    class InvokableCallList
    {
        private readonly List<BaseInvokableCall> m_RuntimeCalls = new List<BaseInvokableCall>();

        public void AddListener(BaseInvokableCall call)
        {
            m_RuntimeCalls.Add(call);
        }

        public void Clear()
        {
            m_RuntimeCalls.Clear();
        }

これを呼び出しているのは RemoveAllListeners でした

public abstract class UnityEventBase : ISerializationCallbackReceiver
{
        public void RemoveAllListeners()
        {
            m_Calls.Clear();
        }
}

つまり、UnityEvent ではデリゲートが List にキャッシュされ、RemoveAllListener もしくは RemoveListener を呼ばない限りキャッシュから消されなさそうです

これは デリゲートを内部で利用した場合 (onClick.AddListener*1;) に限らず問題がありそうなので、UnityEvent に登録した場合は 開放されるタイミングで RemoveAllListener を呼び出したほうが無難かもしれません

*1:) => Delegate?.Invoke(