引用引起的内存泄漏2

  C#中一个对象的函数, 如果被引用了, 也会导致对象无法被回收, 虽然实际使用中几率很小, 还是记录一下.

using UnityEngine;

public class MemoryLeakTest : MonoBehaviour
{
    public class Event
    {
        public void Call()
        {
            Debug.Log("Event Call");
        }

        ~Event()
        {
            Debug.Log("Event Die");
        }
    }

    System.Action call = null;
    Event _event = null;

    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 100, 100, 50), "Event Call"))
        {
            _event = new Event();
            _event.Call();
            call += _event.Call;
            _event = null;
        }
        if(GUI.Button(new Rect(100, 300, 100, 50), "GC"))
        {
            Debug.Log("手动GC");
            System.GC.Collect();
            System.GC.WaitForPendingFinalizers();
        }
    }
}

  创建一个对象, 把对象的Call方法加到Action上, 然后置空引用, 只要对象的方法被引用了, 这样就成了无法GC的对象了.

  这个问题的发生属于个人问题, 那么怎样从结构上来避免呢, 如果使用一个弱引用能否避免呢 : 

using UnityEngine;

public class MonoEvent : MonoBehaviour
{
    public string mark;
    public void Call()
    {
        Debug.Log("MonoEvent Call " + mark);
    }
    ~MonoEvent()
    {
        Debug.Log("MonoEvent Die " + mark);
    }
}

  引用对象MonoEvent

using UnityEngine;
public class WeakEventTest : MonoBehaviour
{
    System.WeakReference weakCall = null;

    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 150, 100, 50), "MonoEvent Call"))
        {
            var monoEvent = new GameObject().AddComponent<MonoEvent>();
            monoEvent.mark = "111";
            monoEvent.Call();
            weakCall = new System.WeakReference(new System.Action(monoEvent.Call));
            UnityEngine.Object.Destroy(monoEvent.gameObject);        
        }
        if(GUI.Button(new Rect(100, 210, 100, 50), "手动Call"))
        {
            var call = weakCall.Target as System.Action;
            if(call != null)
            {
                call.Invoke();
            }
            else
            {
                Debug.Log("引用没了");
            }
        }
        if(GUI.Button(new Rect(100, 300, 100, 50), "GC"))
        {
            Debug.Log("手动GC");
            System.GC.Collect();
            System.GC.WaitForPendingFinalizers();
        }
    }
}

  运行点击 "MonoEvent Call" 后直接就能析构了, 说明弱引用没有影响到GC. 可是反过来看GC是不是会影响弱引用 : 

using UnityEngine;
public class WeakEventTest : MonoBehaviour
{
    System.WeakReference weakCall = null;
    MonoEvent _monoEvent;
    private void OnGUI()
    {
        if(GUI.Button(new Rect(100, 150, 100, 50), "MonoEvent Call"))
        {
            var monoEvent = new GameObject().AddComponent<MonoEvent>();
            monoEvent.mark = "111";
            monoEvent.Call();
            weakCall = new System.WeakReference(new System.Action(monoEvent.Call));
            _monoEvent = monoEvent; // 添加引用, 不被GC
        }
        if(GUI.Button(new Rect(100, 210, 100, 50), "手动Call"))
        {
            var call = weakCall.Target as System.Action;
            if(call != null)
            {
                call.Invoke();
            }
            else
            {
                Debug.Log("引用没了");
            }
        }
        if(GUI.Button(new Rect(100, 300, 100, 50), "GC"))
        {
            Debug.Log("手动GC");
            System.GC.Collect();
            System.GC.WaitForPendingFinalizers();
        }
    }
}

  先创建对象, 然后手动GC, 在看看手动Call能否触发, 结果悲剧了弱引用对象没了:

  这里引用对象引用的是新创建的System.Action, 它是新对象并且只被弱引用引用, 自然会被GC回收掉了, 所以在系统设计上没有办法用弱引用事件来对对象解耦, 达到不影响GC的效果...

 

posted @ 2020-04-03 15:56  tiancaiKG  阅读(809)  评论(0编辑  收藏  举报