- Home /
Does calling Destroy() on a GameObject/MonoBehavior drop references to any event listeners?
I know that source objects (defined as the object who owns the event) will keep a reference to anyone who is listening to it's events, so it's important to make sure the listeners unsubscribe lest you cause a memory leak.
I'm now wondering the reverse - if I call Destroy() on GameObject SourceObject with MonoBehavior SourceScript on it, will that drop references to anyone who is listening to SourceScript's events?
Or, do I have to make sure that all the listeners unsubscribe themselves from SourceScript's events before it gets destroyed?
Answer by Bunny83 · May 15, 2016 at 11:18 AM
Yes they are dropped as long as all references to the source script instance are gone. Keep always in mind that inside the managed world it's not possible to destroy any object. When you call Destroy on a gameobject / Monobehaviour you only destroy the native C++ counterpart. The managed part of the object will remain as long as there is a reference to it somewhere inside the managed world.
If you have a reference to your sourceScript in a thrid script like that:
public MySourceScript script;
and you destroy that instance by calling Destroy on the script or the containing gameobject the source script native part will be destroyed and the managed part will be marked as destroyed but still remains in memory until all references to it are gone so it can be garbage collected.
The script variable above will appear to be null after you call Destroy, but it actually isn't. Unity fakes that the reference is null when the object has been destroyed.
So the actual managed part of the instance will still remain and if it has an event with other subbed objects they will remain as well until the reference to the dead source script instance is gone, In that case the GC will clean up all left over objects.
The check
if (script == null)
script = null;
seems silly but actually has a use in Unity. It removes the fake null reference and allows the GC to collect the managed object.
Of course when you set a new instance to the variable the old reference will be gone as well.
Ah crazy! That's a lot of memory leaks waiting to happen. So Destroy() should never be used without an accompanying "= null"?
Well, only if the variable does persist in some way. In most cases where you destroy objects you don't keep references to those classes. So i wouldn't bother too much about that issue. Those "leaks" are not real leaks. A memory leak is something that keeps the memory loaded but isn't accessible any more. That's not possible in the managed world. So sooner or later the reference will be either overwritten or it goes out of extent. In both cases the object that was referenced is garbage collected.
@Bunny83, can you, please, explain.
If I have two $$anonymous$$onoBehaviours on the same gameObject and assign references in the inspector:
public class A : $$anonymous$$onoBehaviour {
public B b;
void Start () {
DestroyItself();
}
private void DestroyItself(){
b.a = null; // shoud I do this ?
b = null; // shoud I do this ?
Destroy(this);
}
}
//---
public class B : $$anonymous$$onoBehaviour
{
public A a;
}
Should I write these lines to delete $$anonymous$$onoBehaviour properly: b.a = null; and b = null; that GarbageCollector could collect this instance of class A????
Or may be only one of them?
//==================================
//==================================
And other situation: If I have else one $$anonymous$$onoBehaviours on the same gameObject
public class C : $$anonymous$$onoBehaviour
{
public A a;
public B b;
void Start(){
Destroy(a);
Destroy(b);
Destroy(this);
}
}
Shoud I assign a and b to null in this case?
Well, your cases are very abstract. In your first case if "B" should not be destroyed, then yes, setting b.a = null;
would help the GC to actually clean up the destroyed instance of A. In your second case, I'm not sure when this is actually the case. If you actually destroy all those scripts, why is the gameobject still there?
The general rule is simple: As long as there is any static field or any active script with a field the references other monobehaviour scripts, the static variable or the instance fields of the remaining class instance will keep a destroyed object still alive. The reference chain can go as deep as you want. So as long as a class isn't garbage collected (because there's somewhere still a reference to it) it will keep all the instances alive that it references in its own fields. The only exception are circular references between multiple classes which are all no longer referenced by any active object (or a static variable).
Imagine you have a class A that is still alive on a gameobject. There might be other objects (B,C,D,E,F) which all have been destroyed with Destroy. There might be a reference chain like this:
A
|
\|/
B-->C-->D-->E
/|\ |
\___________/
Since A is still alive all those objects will stay alive. However when we clear the reference in class A it becomes this:
A
B-->C-->D-->E
/|\ |
\___________/
B,C,D and F are all destroyed objects and no longer referenced from an active object. So they can be garbage collected since there are only references between objects that are all up for garbage collection.
Thank you, @Bunny83 !
But what do you mean " would help the GC to actually clean up the destroyed instance of A." ?
Would component A collected by GC without this line b.a = null;
?