싱글톤 클래스

김기훈·2025년 2월 10일
0

Unity

목록 보기
5/7

Singleton

프로그램에서 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴이다.
해당 패턴을 사용하면 전역적으로 접근할 수 있는 단일 인스턴스를 유지할 수 있으며, 여러 곳에서 동일한 객체를 공유할 수 있다.

SingletonTemplate<T> 클래스를 상속하기만 하면 Singleton 클래스가 되도록 템플릿을 활용하여 클래스 작성함

Code

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace GH
{
    // Marker Interface
    public interface IScenePersistent { }

    // Singleton Template
    public class SingletonTemplate<T> : MonoBehaviour where T : MonoBehaviour
    {
        private static T instance = null;
        private static readonly object locker = new object();

        public static T Instance
        {
            get
            {
                if (null == instance)
                {
                    lock (locker)
                    {
                        instance = FindByType();
                        if (null == instance)
                        {
                            Debug.Log("Create " + typeof(T).Name);
                            GameObject obj = new GameObject(typeof(T).Name);
                            instance = obj.AddComponent<T>();

                            if (instance is IScenePersistent)
                            {
                                DontDestroyOnLoad(obj);
                            }
                        }
                    }
                }

                return instance;
            }
        }

        protected virtual void Awake()
        {
            // 중복된 객체 생성 방지
            if (instance != null && instance != this)
            {
                Debug.LogWarning("Duplicate " + typeof(T).Name 
                		+ " instance found, destroying this one.");
                Destroy(gameObject);
                return;
            }

            instance = this as T;   // this가 T로 형변환이 불가능하면 null 반환
            if (instance is IScenePersistent)
            {
                DontDestroyOnLoad(gameObject);
            }

        }

        // 싱글톤 비활성화
        public static void DisableSingleton()
        {
            Debug.Log("Disable " + typeof(T).Name);
            if(null != instance)
            {
                instance.gameObject.SetActive(false);
            }
        }

        // 싱글톤 활성화
        public static void EnableSingleton()
        {
            Debug.Log("Enable " + typeof(T).Name);
            if (null != instance)
            {
                instance.gameObject.SetActive(true);
            }
        }

        // 싱글톤 삭제
        public static void DestroySingleton()
        {
            if (instance != null)
            {
                Debug.Log("Destroy " + typeof(T).Name);
                Destroy(instance.gameObject);
                instance = null;
            }
        }

        // T 타입 컴포넌트 탐색
        private static T FindByType()
        {
            List<T> allObjects = new List<T>();
            Scene curScene = SceneManager.GetActiveScene();
            GameObject[] rootObjects = curScene.GetRootGameObjects();

            foreach (var rootObject in rootObjects)
            {
                allObjects.AddRange(rootObject.GetComponentsInChildren<T>(true));
            }

            if (1 == allObjects.Count)
            {
                return allObjects[0];
            }
            else if (1 < allObjects.Count)
            {
                Debug.LogError("Multiple instance of " + typeof(T).Name + " exist.");
            }

            return null;
        }

    }
}

Tip

▪️Marker Interface

마커 인터페이스 (IScenePersistent)를 활용하여 DontDestroyOnLoad() 의 적용여부를 판별함

if (instance is IScenePersistent)
{
	DontDestroyOnLoad(gameObject);
}

▪️lock

여러 스레드가 동시에 실행되지 않도록 보호하며, 먼저 접근한 스레드가 lock 을 걸어 한 번에 하나의 스레드만 lock 블록 안의 코드를 실행하고 다른 스레드들은 대기한다.

싱글톤, 멀티 스레드 환경에서 공유 리소스 보호목적으로 활용하며, 성능저하를 막기위해 lock 블록 안에서는 최소한의 작업만 수행하도록 한다.

private static readonly object lockObj = new object();

lock(lockObj)    // thread safe
{
	// logic
}

static readonly 키워드 사용 이유

  1. static 이 없는 경우 여러 개의 인스턴스가 존재할 수 있는 경우, 서로 다른 lockObj를 사용하게 되어 스레드 동기화가 깨질 수 있다.
    반면에, static을 붙이면 lockObj는 클래스 전체에서 하나만 존재하기에, 모든 스레드가 동일한 lockObj를 공유하게 되고 한 번에 하나의 스레드만 lock 블록을 실행함을 보장할 수 있다.

  2. readonly 키워드를 사용하면 lockObj가 생성된 이후에 변경할 수 없다. static 만 붙이고 readonly 를 사용하지 않으면 어떤 코드에서 lockObj에 새로운 객체를 할당할 수 있다.
    이렇게 되면 lock(lockObj)가 다른 객체를 참조하게되어 동기화가 깨질 위험이 있다.

profile
Late Bloomer

0개의 댓글