Locating Dependencies

Most components don’t exist in isolation. Sooner or later, they’re going to need to talk to each other, which raises the question of how they should acquire references to one another. In Unity, this is easily accomplished in the editor: expose the type you’re looking for as a public field, then wire it up in the inspector by dragging an instance of the dependency onto the field. I actually really like this approach! It’s essentially a form of dependency injection. This works well for objects in a scene, or relationships between different scripts in a prefab, but it isn’t well suited for items instantiated at runtime. It’s also brittle, since changing the type expected or provided will break the association. These situations call for a different approach to locating dependencies.

The default and simplest answer is FindObjectOfType. This function makes it easy to just look up what you need in the scene when you need it. Unfortunately, FindObjectOfType is slow. It has to walk the scene to find a matching component, not something you want to be doing every frame. Excessive and inappropriate use of FindObjectOfType is a major cause of performance issues for new users, and it cannot be warned against enough. The Unity documentation even throws it under the bus!

“Please note that this function is very slow. It is not recommended to use this function every frame. In most cases you can use the singleton pattern instead.”

Unfortunately, I’m really disappointed by their recommended alternative. Almost every document or tutorial on Unity suggest singletons as the cure-of-choice for instance-locating issues, and it makes me pull my hair out. It’s not that the pattern is inherently bad – an utter affront of what I would consider beautiful code, sure, but it’s a decent enough shortcut in certain situations. But developer does not live by shortcut alone; we should strive to follow best practices wherever possible, and should feel some nonzero amount of dirty for doing otherwise.

Let’s look at a sample implementation of the Singleton pattern in Unity:

public class TypicalSingleton : MonoBehaviour
{
    private static TypicalSingleton instance;

    public static TypicalSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<TypicalSingleton>();
            }

            return instance;
        }
    }
}

This code makes it possible to globally access an instance of the TypicalSingleton class. It still relies on FindObjectOfType, but the value is cached, so the cost only needs to be paid once. This approach still expects an instance in the scene; a more authentic singleton would lazily instantiate itself when an instance was first requested. On the other hand, that limits how the class can be configured or wired up in the editor. Still, for the sake of argument, let’s support both workflows:

    public static TypicalSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<TypicalSingleton>();
                if (instance == null)
                {
                    var gameObject = new GameObject();
                    instance = gameObject.AddComponent<TypicalSingleton>();
                }
            }

            return instance;
        }
    }

Another thing to note is that this class, while now globally accessible, is still not a singleton. If it isn’t destroyed between scenes, multiple instances can be created in the process of loading scenes. To combat this, additional logic needs to be added to enforce singularity:

    internal void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

Now we have a class that is accessible globally and only exists as a single instance. But we’re not done. To see why, let’s observe our singleton in action:

    internal void Start()
    {
        TypicalSingleton.Instance.OnEvent += this.OnEvent;
    }

    internal void OnDestroy()
    {
        TypicalSingleton.Instance.OnEvent -= this.OnEvent;
    }

Personally, I prefer to wire up events in OnEnable and OnDisable, but code like the above is not uncommon in the wild. It’s also a common cause of scene object leaks. If TypicalSingleton is destroyed when loading a new scene, it’s entirely possible that it will be destroyed before the script that consumes it, in which case the instance getter will erroneously create a new instance, which will then be left behind in the scene. That’s okay, we can get around this with more code:

    public static bool Exists
    {
        get
        {
            return instance != null;
        }
    }
    internal void OnDestroy()
    {
        if (TypicalSingleton.Exists)
        {
            TypicalSingleton.Instance.OnEvent -= this.OnEvent;
        }
    }

Seems reasonable enough, but does anyone else think this shortcut is starting to get pretty long?

Process

Before we continue writing code, let’s take a step back and remember why we needed a singleton in the first place: it’s a shortcut to acquiring an instance of a dependency. The problem is that the shortcut makes it so easy to access that instance, we use the static getter everywhere instead of keeping a local reference. This is bad practice! When that dependency reference was a field we wired up in the editor, we were able to, say, check whether it was null without adding extra code to the dependency. By sprinkling references to a singleton throughout the consumer, we also make it impossible to unit test. If we’re going to use singletons, let’s get into the habit of using them to replace (or supplement) the editor wire-up. Cache a reference to the instance on Start, then use it as you would any other dependency:

    public TypicalSingleton typicalSingleton;

    internal void Start()
    {
        if (this.typicalSingleton == null)
        {
            this.typicalSingleton = TypicalSingleton.Instance;
        }

        this.typicalSingleton.OnEvent += this.OnEvent;
    }

    internal void OnDestroy()
    {
        if (this.typicalSingleton != null)
        {
           this.typicalSingleton.OnEvent -= this.OnEvent;
        }
    }

This alleviates some of the issues with the singleton pattern. Additionally, once we stop using the global access as a crutch*, the static instance getter starts to seem a little superfluous. In this example, we could easily replace it with FindObjectOfType again. We’d only take a hit once, just like the singleton and with much less boilerplate code. But what if we want to access the dependency from multiple consumers? Isn’t there still value in caching the instance to reduce the cost of looking it up? Of course, but we don’t necessarily need the singleton pattern for that. I’ll go over my approach to this problem in my next update.

*Note: There are perfectly legitimate reasons to need global access to some piece of functionality. In those cases, a singleton might be the right call. I find a static class usually works just as well, without the illusion of trying to have your cake (good OO practice) and eat it too. My global event dispatcher, for example, is a static class, but could just as easily be a true singleton. In my opinion, replacing GlobalEventAggregator.GetEvent with GlobalEventAggregator.Instance.GetEvent gains me nothing but unnecessary finger fatigue.

Bookmark the permalink.

Comments are closed