앤디가이 블로그

Unity와 C#을 통한 싱글톤 패턴(Singleton Pattern)의 정의와 사용 방법에 대해 알아보자.

 

1. 싱글톤 패턴(Singleton Pattern)이란?

싱글톤 패턴(Singleton Pattern)은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공하는 디자인 패턴을 말한다.

싱글톤 패턴은 클래스에서 하나뿐인 인스턴스를 관리하도록 만들어야 되며, 다른 어떤 클래스에서도 자신의 인스턴스를 추가로 만들지 못하게 해야 한다. 혹여나 인스턴스가 필요하다면 반드시 클래스 자신을 거치도록 해야 한다.

유니티에서는 주로 Awake() 함수에서 인스턴스를 할당하고, Destroy() 함수에서 인스턴스를 해제해준다.

싱글톤 객체는 그 인스턴스에 접근할 수 있도록 전역 접근자를 제공해야 한다. 언제 어디서든 필요하면 해당 클래스에 요청할 수 있게 만들어 줘야 한다.

 

2. 싱글톤 패턴(Singleton Pattern) 어디에 사용하면 좋을까?

싱글톤 패턴(Singleton Pattern)은 인스턴스를 하나만 가지고 있는 객체에 적용하면 좋기 때문에 대화 상자 객체, 사용자 설정 객체, 로그 기록용 객체, 디바이스 드라이버 객체 등 프로그램에서 하나의 인스턴스만 있어도 되는 공통 항목에 사용하면 좋다.

유니티에서 예를 들면 게임의 흐름을 주관하는 게임 매니저, 공통으로 팝업을 관리하는 팝업 매니저, 사운드를 처리해주는 사운드 매니저 클래스처럼 프로그램 내 하나만 존재해야 되는 매니저 클래스 등에 많이 활용된다. 

씬이 바뀜에 따라 매니저를 다시 생성하고 할당해주는 작업을 하지 않아도 되니 상당히 편리한 개발 패턴이고, 실제로 가장 많이 활용되고 있는 패턴이지 않을까 생각이 든다.

 

3. 유니티에서 싱글톤 클래스 만드는 방법

유니티에서 클래스를 싱글톤(하나의 유일무이 객체) 객체로 만들기 위해서는 다양한 방법이 존재하는데 편한 방식에 따라 구현하면 좋다.

 

1. 가장 간단한 방법

싱글톤 객체를 만드는 건 몇가지 있는데 그중 가장 간단하게 코딩하는 방법은 프로퍼티를 사용하는 방법이다.

인스턴스를 넣어주는 작업은 외부에서 하면 안 되기 때문에 private set으로 설정하고, 외부에서 해당 객체를 가져올 수 있도록 get을 통해 열어준다.

유니티 기본 함수인 Awake()함수에서 Instance에 본인 클래스 객체를 할당해주며, Destroy() 함수에서 해제해준다.

아래 코드를 참고해보자.

using UnityEngine;

namespace SingletonPattern
{
    public class GameManager : MonoBehaviour
    {
        // Static 변수를 통해 해당 인스턴스를 외부에서 접근 가능하게 함.
        public static GameManager Instance { get; private set; }

        private void Awake()
        {
            Instance = this;
        }
        private void OnDestroy()
        {
            Instance = null;
        }
    }
}

다만 위 방법의 단점은 해당 GameManager 스크립트가 붙어있는 GameObject가 Scene에 존재하고 있어야 되는 점이다. 현재 Scene에서 GameManager를 가지고 있는 GameObject가 없다면, null 레퍼런스 익셉션이 발생할 거다.

 

2. 개선된 방법

위 코드에 좀 더 예외처리를 해보자. 위의 간편한 방식은 Instance가 null일 경우 대처하지 못한다. 따라서 Instance가 null일 경우, GameObject를 하나 생성하고, 생성된 GameObject에 GameManager 컴포넌트를 Attach 한 후, 해당 컴포넌트를 인스턴스에 넣어주는 작업을 통해 보완이 가능하다.

코드로 보면 아래와 같다.

using UnityEngine;

namespace SingletonPattern
{
    public class GameManager : MonoBehaviour
    {
        private static GameManager _instance = null;

        // Static 변수를 통해 해당 인스턴스를 외부에서 접근 가능하게 함.
        public static GameManager Instance {
            get {
                if(_instance == null)
                {
                    //인스턴스가 없을 경우, GameObject를 생성 후 인스턴스를 만들어 준다.
                    GameObject obj = new GameObject("GameManager");
                    obj.AddComponent<GameManager>();
                    _instance = obj.GetComponent<GameManager>();

                }
                return _instance;
            }
            private set { _instance = value; }
        }

        private void Awake()
        {
            _instance = this;
        }
        private void OnDestroy()
        {
            _instance = null;
        }
        public void Call()
        {
            Debug.Log("Call");
        }
    }
}

 다른 스크립트에서 호출하는 방식은 다음과 같다.

GameManager.Instance.Call();

 

3. 제네릭 클래스를 이용하는 방법

위 코드를 보면, 예외처리는 되었지만, 저런 매니저 싱글톤 클래스가 많아진다면, 똑같은 코드를 반복해서 작성하는 일은 매우 비효율적이다. 따라서 싱글톤을 만드는 클래스를 별도로 분리하고, 해당 클래스를 상속받는 객체를 싱글톤으로 만들어 주는 방법이 있다.

싱글톤 클래스를 별도로 만들면 코드는 아래와 같다.

using UnityEngine;

namespace SingletonPattern
{
    public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    {
        private static T _instance;

        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = (T)FindObjectOfType(typeof(T));

                    if (_instance == null)
                    {
                        GameObject obj = new GameObject();
                        _instance = obj.AddComponent(typeof(T)) as T;
                        obj.name = typeof(T).ToString();

                        //모든 Scene에서 살아 있어야 될 때 활성화.
                        DontDestroyOnLoad(obj);
                    }
                }
                return _instance;
            }
        }
    }
}

다시 GameManager 클래스를 수정해보면, 코드가 다음과 같이 상당히 짧아진다. 

싱글톤을 만드는 코드를 전부 Singleton<T> 클래스로 이관했기 때문이다.

using UnityEngine;

namespace SingletonPattern
{
    public class GameManager : Singleton<GameManager>
    {
        public void Call()
        {
            Debug.Log("Call");
        }
    }
}

호출 코드는 동일하다.

GameManager.Instance.Call();

 

4. 정리

싱글톤 패턴은 만들기 쉽고 다른 스크립트에서 워낙 강력한 접근성을 제공하기에 많은 개발자들이 선호하는 개발 패턴이다.

하지만 싱글톤으로 구현해 놓고, 왜 싱글톤으로 만들었는지 설명하지 못하는 개발자도 많다.

싱글톤을 남발한다면, 코드의 추적이 어려워지는 단점도 존재하며, private 객체를 가지고 있어야 하므로 상속하면 안 되는 구조적 한계도 있다.

프로그램 내 한 가지 기능만을 위한 하나의 인스턴스만 필요한 경우에 싱글톤을 적용해야 한다는 생각을 가지고 싱글톤 패턴을 적용하면 좋을 것 같다.

공유하기

facebook twitter kakaoTalk kakaostory naver band