- Home /
How to unsubscribe from a delegate that is inside of a singleton?
In my GameManager, I have a delegate. In my Door script, I subscribe to this delegate. I've tried unsubscribing from it in both the OnDisable and OnDestroy methods in the Door script. In both cases, I get an error when I stop running the game in the editor:
Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)
Using Debug.Log, I found out that this is because the GameManager is getting destroyed before the Door script. Even if I do a null check inside either the OnDisable or OnDestroy of the Door script to see if the GameManager is null, I get the same error
if (GameManager.Instance)
{
GameManager.Instance.OnAllEnemiesKilled -= OpenDoor;
}
Somebody told me that I don't need to unsubscribe from it, as the delegate will automatically become null when the Door object is destroyed, but that's not true. During runtime, after the Door is destroyed, my update loop inside of the GameManager is still printing that the delegate has one subscriber: the Door.
You could put it in a try-catch block to make it go away if it only happens in the editor. Won't bother you in a build.
If you know the error will occur, don't hide it under a blanket. Fix the problem.
Unfortunately, this try catch doesn't seem to work
private void OnDisable()
{
try
{
Game$$anonymous$$anager.Instance.OnAllEnemies$$anonymous$$illed -= OpenDoor;
}
catch (Exception e)
{
Debug.LogError(e, this);
}
}
Answer by Bunny83 · Apr 18, 2018 at 10:29 PM
The problem is your singleton implementation. When you stop the game the singleton object will be destroyed as well. However since you access "Instance" in OnDestroy you will recreate the manager. There are several solutions to this problem. First of all this is mainly an issue in the editor. When OnDestroy is called due to stopping playmode the environment is currently resetting / reloading the previously serialized state. When you create objects at this stage they would interfer with the scene representation during edit time.
One solution would be to avoid recreating the singleton when it got destroyed. For this you could simply add a static bool to the singleton which you set to true in the OnDestroy method of the singleton. Inside your Instance getter you only create an instance when that static variable is false. Of course that would mean you can't rely on the Instance returning an object when used in OnDestory.
Another way is to bypass the overloaded "==" operator in your singleton implementation and do an actual reference equal check. Even when the singleton gets destroyed, the managed object is still alive but a null check would usually return true.
So instead of
if (m_Instance == null)
you could simply do
if ((object)m_Instance == null)
Note that this approach does not work if your singleton instance can actually be destroyed during runtime as that would mean it can never be recreated.
The if ((object)m_Instance == null)
solution worked perfectly. Thank you :) I call DontDestroyOnLoad(this.gameObject);
in the Awake function of the singleton, so I don't anticipate any problems with your solution. Thank you so much! Should I still unsubscribe from the event like this
If(Game$$anonymous$$anager.Instance)
Game$$anonymous$$anager.Instance.OnAllEnemies$$anonymous$$illed -= OpenDoor;
or should I leave the if statement out of it? Also, I subscribe to the delegate in the Start function whereas some other people do it in the OnEnable function. Which one do you think I should use? Thanks again!
You wouldn't need to check if Instance is null since the property will actually return an object. Even when a UnityEngine.Object derived object has been "destroyed" it's still there in the managed world. It just became a fake-null object. So this check:
If(Game$$anonymous$$anager.Instance)
would fail on fake null objects and you would skip the unsubscribing. However in your case it doesn't really matter if you unsubscribe or not when you actually exit playmode ^^
Great, one last thing, should I subscribe in the Start method or in the OnEnable method?
Answer by Hellium · Apr 18, 2018 at 08:50 PM
Have you tried to simply set to null
the event in the OnDestroy
/ OnDisable
of your GameManager
? https://stackoverflow.com/questions/91778/how-to-remove-all-event-handlers-from-an-event
That doesn't seem to work, unless I stop trying to unsubscribe from it in my Door script.
If you set to null the event in the Game$$anonymous$$anager
, you don't need to unsubscribe in the other classes ;)
But that means it won't unsubscribe during runtime, it will only unsubscribe after the game stops running. Don't I want it to unsubscribe during runtime, after the door is destroyed?