DI 추가
This commit is contained in:
8
Assets/Scripts/UVC/core/Injector/Attributes.meta
Normal file
8
Assets/Scripts/UVC/core/Injector/Attributes.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5c0eb21fa2240f47844cee075aeda8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
247
Assets/Scripts/UVC/core/Injector/Attributes/InjectAttribute.cs
Normal file
247
Assets/Scripts/UVC/core/Injector/Attributes/InjectAttribute.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 필드 또는 프로퍼티에 의존성 주입을 표시하는 어트리뷰트
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>Injector가 이 어트리뷰트가 붙은 멤버에 자동으로 의존성을 주입합니다.</para>
|
||||
/// <para>필드(Field)와 프로퍼티(Property) 모두에 사용할 수 있습니다.</para>
|
||||
///
|
||||
/// <para><b>[ 주입 시점 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>MonoBehaviour: InjectorSceneContext.PerformSceneInjection() 시점</description></item>
|
||||
/// <item><description>순수 C# 클래스: Injector가 인스턴스 생성 직후 자동 주입</description></item>
|
||||
/// <item><description>수동: Injector.Inject(target) 또는 InjectGameObject(go) 호출 시</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 기본 사용법 ]</b></para>
|
||||
/// <code>
|
||||
/// public class PlayerController : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger;
|
||||
/// [Inject] private IAudioManager _audioManager;
|
||||
///
|
||||
/// private void Start()
|
||||
/// {
|
||||
/// _logger.Log("PlayerController initialized");
|
||||
/// _audioManager.PlaySFX("spawn");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 선택적 의존성 (Optional) ]</b></para>
|
||||
/// <para>Optional = true로 설정하면 서비스가 등록되지 않은 경우에도 예외가 발생하지 않습니다.</para>
|
||||
/// <code>
|
||||
/// public class DebugOverlay : MonoBehaviour
|
||||
/// {
|
||||
/// // 디버그 서비스가 없어도 동작해야 함
|
||||
/// [Inject(Optional = true)]
|
||||
/// private IDebugService _debugService;
|
||||
///
|
||||
/// private void Update()
|
||||
/// {
|
||||
/// // null 체크 필요
|
||||
/// _debugService?.DrawStats();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 순수 C# 클래스에서 사용 ]</b></para>
|
||||
/// <para>MonoBehaviour가 아닌 일반 클래스에서도 의존성 주입이 가능합니다.</para>
|
||||
/// <code>
|
||||
/// public class GameService : IGameService
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger;
|
||||
/// [Inject] private ISettingsManager _settings;
|
||||
///
|
||||
/// public void Initialize()
|
||||
/// {
|
||||
/// _logger.Log($"Language: {_settings.Language}");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 프로퍼티 주입 ]</b></para>
|
||||
/// <code>
|
||||
/// public class UIManager : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject]
|
||||
/// public ILogService Logger { get; private set; }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 주의사항 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>private 필드에도 주입이 가능합니다 (리플렉션 사용)</description></item>
|
||||
/// <item><description>상속 관계에서 부모 클래스의 [Inject] 필드도 주입됩니다 (Inherited = true)</description></item>
|
||||
/// <item><description>한 멤버에 여러 번 사용할 수 없습니다 (AllowMultiple = false)</description></item>
|
||||
/// <item><description>순환 의존성이 있으면 예외가 발생합니다</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="IInjector.Inject"/>
|
||||
/// <seealso cref="IInjector.InjectGameObject"/>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class InjectAttribute : System.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 선택적 의존성 여부 - true면 서비스가 없어도 예외 발생 안 함
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 기본값 ]</b> false (필수 의존성)</para>
|
||||
///
|
||||
/// <para><b>[ true일 때 동작 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>서비스가 등록되지 않은 경우 null이 주입됨</description></item>
|
||||
/// <item><description>예외가 발생하지 않음</description></item>
|
||||
/// <item><description>코드에서 null 체크가 필요함</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ false일 때 동작 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>서비스가 등록되지 않은 경우 InvalidOperationException 발생</description></item>
|
||||
/// <item><description>필수 의존성임을 명시</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 필수 의존성 - 없으면 예외 발생
|
||||
/// [Inject] private ILogService _logger;
|
||||
///
|
||||
/// // 선택적 의존성 - 없으면 null
|
||||
/// [Inject(Optional = true)] private IAnalyticsService _analytics;
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public bool Optional { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID로 등록된 서비스를 주입 (향후 확장용)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 용도 ]</b></para>
|
||||
/// <para>동일한 인터페이스로 여러 구현체가 등록된 경우 특정 구현체를 선택할 때 사용합니다.</para>
|
||||
/// <para>현재 버전에서는 구현되지 않았으며, 향후 Named Registration 기능 추가 시 사용됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 예정된 사용법 ]</b></para>
|
||||
/// <code>
|
||||
/// // 등록 (향후 지원 예정)
|
||||
/// Injector.Register<ILogger, FileLogger>("file");
|
||||
/// Injector.Register<ILogger, ConsoleLogger>("console");
|
||||
///
|
||||
/// // 주입
|
||||
/// [Inject(Id = "file")] private ILogger _fileLogger;
|
||||
/// [Inject(Id = "console")] private ILogger _consoleLogger;
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public string Id { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 기본 생성자
|
||||
/// </summary>
|
||||
public InjectAttribute() { }
|
||||
|
||||
/// <summary>
|
||||
/// ID를 지정하는 생성자
|
||||
/// </summary>
|
||||
/// <param name="id">서비스 식별자</param>
|
||||
public InjectAttribute(string id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클래스를 자동으로 Injector에 등록하도록 표시하는 어트리뷰트
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>이 어트리뷰트가 붙은 클래스는 자동으로 Injector에 등록될 수 있습니다.</para>
|
||||
/// <para>리플렉션을 통해 어셈블리를 스캔하여 자동 등록하는 기능에서 사용됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 주의사항 ]</b></para>
|
||||
/// <para>현재 버전에서는 자동 스캔 기능이 구현되지 않았습니다.</para>
|
||||
/// <para>InjectorAppContext 또는 InjectorSceneContext에서 수동으로 등록해야 합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 예정된 사용법 ]</b></para>
|
||||
/// <code>
|
||||
/// // 클래스에 어트리뷰트 추가
|
||||
/// [Register(typeof(ILogService), ServiceLifetime.App)]
|
||||
/// public class ConsoleLogger : ILogService
|
||||
/// {
|
||||
/// public void Log(string message) => Debug.Log(message);
|
||||
/// }
|
||||
///
|
||||
/// // 자동 등록 (향후 지원 예정)
|
||||
/// Injector.RegisterFromAssembly(typeof(ConsoleLogger).Assembly);
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 다중 인터페이스 등록 ]</b></para>
|
||||
/// <para>AllowMultiple = true이므로 하나의 클래스에 여러 번 사용할 수 있습니다.</para>
|
||||
/// <code>
|
||||
/// [Register(typeof(ILogService), ServiceLifetime.App)]
|
||||
/// [Register(typeof(IDebugLogger), ServiceLifetime.App)]
|
||||
/// public class MultiLogger : ILogService, IDebugLogger
|
||||
/// {
|
||||
/// // 두 인터페이스로 모두 등록됨
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="ServiceLifetime"/>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class RegisterAttribute : System.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 등록할 인터페이스 타입
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>null이면 자기 자신 타입으로 등록됩니다.</para>
|
||||
/// <para>인터페이스 또는 부모 클래스 타입을 지정할 수 있습니다.</para>
|
||||
/// </remarks>
|
||||
public Type InterfaceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 라이프사이클
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>App: 앱 전체, Scene: 현재 씬, Transient: 매번 새로 생성</para>
|
||||
/// </remarks>
|
||||
public ServiceLifetime Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 자기 자신 타입으로 등록하는 생성자
|
||||
/// </summary>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// [Register(ServiceLifetime.App)]
|
||||
/// public class SettingsManager { }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public RegisterAttribute(ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
{
|
||||
InterfaceType = null;
|
||||
Lifetime = lifetime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 인터페이스 타입으로 등록하는 생성자
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">등록할 인터페이스 타입</param>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// [Register(typeof(ILogService), ServiceLifetime.App)]
|
||||
/// public class ConsoleLogger : ILogService { }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public RegisterAttribute(Type interfaceType, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
{
|
||||
InterfaceType = interfaceType;
|
||||
Lifetime = lifetime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9e646e0bb06fb5429ba49d47c21b415
|
||||
498
Assets/Scripts/UVC/core/Injector/IInjector.cs
Normal file
498
Assets/Scripts/UVC/core/Injector/IInjector.cs
Normal file
@@ -0,0 +1,498 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 의존성 주입(DI) 컨테이너 인터페이스
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>서비스의 등록, 해결, 주입, 라이프사이클 관리를 담당하는 DI 컨테이너의 인터페이스입니다.</para>
|
||||
/// <para>IDisposable을 구현하여 리소스 정리를 지원합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 주요 기능 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>Registration:</b> 서비스 등록 (Register, RegisterInstance, RegisterPrefab, RegisterSingleton, RegisterFactory)</description></item>
|
||||
/// <item><description><b>Resolution:</b> 서비스 해결 (Resolve, TryResolve, IsRegistered)</description></item>
|
||||
/// <item><description><b>Injection:</b> 의존성 주입 (Inject, InjectGameObject)</description></item>
|
||||
/// <item><description><b>Lifecycle:</b> 라이프사이클 관리 (OnSceneUnloaded, ClearServices)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 4가지 등록 타입 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>타입</term>
|
||||
/// <description>등록 메서드</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>Type A (순수 C#)</term>
|
||||
/// <description>Register<TInterface, TImpl>()</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type B (MonoBehaviour)</term>
|
||||
/// <description>Register<TInterface, TImpl>() - TImpl이 MonoBehaviour인 경우</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type C (Prefab)</term>
|
||||
/// <description>RegisterPrefab<T>()</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type D (Singleton)</term>
|
||||
/// <description>RegisterSingleton<T>()</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 체이닝 지원 ]</b></para>
|
||||
/// <para>모든 등록 메서드는 IInjector를 반환하여 메서드 체이닝을 지원합니다.</para>
|
||||
/// <code>
|
||||
/// Injector
|
||||
/// .Register<ILogService, ConsoleLogger>(ServiceLifetime.App)
|
||||
/// .Register<IAudioManager, AudioManager>(ServiceLifetime.App)
|
||||
/// .RegisterSingleton<SettingsManager>();
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 구현체 ]</b></para>
|
||||
/// <para>이 인터페이스의 구현체는 <see cref="Injector"/> 클래스입니다.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="InjectAttribute"/>
|
||||
/// <seealso cref="ServiceLifetime"/>
|
||||
public interface IInjector : IDisposable
|
||||
{
|
||||
#region Registration - 서비스 등록
|
||||
|
||||
/// <summary>
|
||||
/// 인터페이스와 구현 타입을 등록합니다. (Type A / Type B)
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">서비스 인터페이스 타입</typeparam>
|
||||
/// <typeparam name="TImplementation">구현 클래스 타입</typeparam>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>TImplementation이 MonoBehaviour인지에 따라 Type A 또는 Type B로 자동 판별됩니다.</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>순수 C# 클래스: new T()로 인스턴스 생성</description></item>
|
||||
/// <item><description>MonoBehaviour: 새 GameObject를 생성하고 AddComponent 수행</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Type A: 순수 C# 클래스
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type B: MonoBehaviour (자동 판별)
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector Register<TInterface, TImplementation>(ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class
|
||||
where TImplementation : class, TInterface;
|
||||
|
||||
/// <summary>
|
||||
/// 타입을 자기 자신으로 등록합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">등록할 타입 (인터페이스가 아닌 구체 클래스)</typeparam>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>인터페이스 없이 구체 클래스를 직접 등록할 때 사용합니다.</para>
|
||||
/// <para>내부적으로 Register<T, T>()를 호출합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 인터페이스 없이 직접 등록
|
||||
/// Injector.Register<GameManager>(ServiceLifetime.App);
|
||||
///
|
||||
/// // 사용
|
||||
/// var manager = Injector.Resolve<GameManager>();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector Register<T>(ServiceLifetime lifetime = ServiceLifetime.App) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 이미 생성된 인스턴스를 등록합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">서비스 인터페이스 타입</typeparam>
|
||||
/// <param name="instance">등록할 인스턴스 (null 불가)</param>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <exception cref="ArgumentNullException">instance가 null인 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>외부에서 생성한 인스턴스를 Injector에 등록합니다.</para>
|
||||
/// <para>등록된 인스턴스에 대해서는 의존성 주입이 수행되지 않습니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 사례 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Context 자신을 등록할 때</description></item>
|
||||
/// <item><description>외부 라이브러리에서 생성한 인스턴스를 등록할 때</description></item>
|
||||
/// <item><description>테스트에서 Mock 객체를 등록할 때</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Context 자신을 등록
|
||||
/// Injector.RegisterInstance<InjectorAppContext>(this, ServiceLifetime.App);
|
||||
///
|
||||
/// // 외부에서 생성한 인스턴스 등록
|
||||
/// var config = LoadConfigFromFile();
|
||||
/// Injector.RegisterInstance<IAppConfig>(config);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector RegisterInstance<TInterface>(TInterface instance, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class;
|
||||
|
||||
/// <summary>
|
||||
/// Prefab을 기반으로 MonoBehaviour를 등록합니다. (Type C)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">MonoBehaviour 타입</typeparam>
|
||||
/// <param name="prefab">인스턴스화할 Prefab (null 불가)</param>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <exception cref="ArgumentNullException">prefab이 null인 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>Resolve 시 등록된 Prefab을 Instantiate하여 인스턴스를 생성합니다.</para>
|
||||
/// <para>Inspector에서 설정한 값들이 유지됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Inspector에서 할당한 Prefab 등록
|
||||
/// [SerializeField] private UIManager uiManagerPrefab;
|
||||
///
|
||||
/// protected override void RegisterServices()
|
||||
/// {
|
||||
/// Injector.RegisterPrefab(uiManagerPrefab, ServiceLifetime.App);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector RegisterPrefab<T>(T prefab, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where T : MonoBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Prefab GameObject를 기반으로 특정 컴포넌트(인터페이스)를 등록합니다. (Type C)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">컴포넌트 타입 (인터페이스 또는 클래스)</typeparam>
|
||||
/// <param name="prefab">인스턴스화할 Prefab GameObject (null 불가)</param>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <exception cref="ArgumentNullException">prefab이 null인 경우</exception>
|
||||
/// <exception cref="ArgumentException">Prefab에 T 타입 컴포넌트가 없는 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>인터페이스 타입으로 등록할 때 사용합니다.</para>
|
||||
/// <para>Prefab의 GameObject에서 T 타입 컴포넌트를 찾아 등록합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 인터페이스로 등록
|
||||
/// [SerializeField] private GameObject uiManagerPrefab; // UIManager 컴포넌트 포함
|
||||
///
|
||||
/// protected override void RegisterServices()
|
||||
/// {
|
||||
/// Injector.RegisterPrefab<IUIManager>(uiManagerPrefab, ServiceLifetime.App);
|
||||
/// }
|
||||
///
|
||||
/// // 사용
|
||||
/// [Inject] private IUIManager _uiManager;
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector RegisterPrefab<T>(GameObject prefab, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 Singleton 클래스를 Injector에 연동합니다. (Type D)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Singleton<T>, SingletonApp<T>, SingletonScene<T> 상속 클래스</typeparam>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>기존 Singleton 패턴을 사용하는 클래스를 [Inject] 어트리뷰트로도 주입받을 수 있게 합니다.</para>
|
||||
/// <para>Resolve 시 T.Instance 프로퍼티를 통해 인스턴스를 반환합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 라이프사이클 자동 결정 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Singleton<T>: App (순수 C#)</description></item>
|
||||
/// <item><description>SingletonApp<T>: App (MonoBehaviour)</description></item>
|
||||
/// <item><description>SingletonScene<T>: Scene (MonoBehaviour)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 등록
|
||||
/// Injector.RegisterSingleton<SettingsManager>();
|
||||
/// Injector.RegisterSingleton<NetworkManager>();
|
||||
///
|
||||
/// // [Inject] 사용
|
||||
/// [Inject] private SettingsManager _settings;
|
||||
///
|
||||
/// // 기존 방식도 동작
|
||||
/// SettingsManager.Instance.Save();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector RegisterSingleton<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 팩토리 함수를 사용하여 서비스를 등록합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">서비스 인터페이스 타입</typeparam>
|
||||
/// <param name="factory">인스턴스 생성 팩토리 함수 (null 불가)</param>
|
||||
/// <param name="lifetime">서비스 라이프사이클 (기본값: App)</param>
|
||||
/// <returns>체이닝을 위한 IInjector 인스턴스</returns>
|
||||
/// <exception cref="ArgumentNullException">factory가 null인 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>커스텀 로직으로 인스턴스를 생성해야 할 때 사용합니다.</para>
|
||||
/// <para>팩토리 함수는 IInjector를 파라미터로 받아 다른 서비스에 의존할 수 있습니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 사례 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>생성자에 파라미터가 필요한 경우</description></item>
|
||||
/// <item><description>조건부 인스턴스 생성이 필요한 경우</description></item>
|
||||
/// <item><description>다른 서비스를 명시적으로 주입해야 하는 경우</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 팩토리 함수로 등록
|
||||
/// Injector.RegisterFactory<ISceneConfig>(injector => new SceneConfig
|
||||
/// {
|
||||
/// SceneName = SceneManager.GetActiveScene().name,
|
||||
/// Difficulty = 1.5f
|
||||
/// }, ServiceLifetime.Scene);
|
||||
///
|
||||
/// // 다른 서비스에 의존하는 경우
|
||||
/// Injector.RegisterFactory<IPlayerService>(injector =>
|
||||
/// {
|
||||
/// var logger = injector.Resolve<ILogService>();
|
||||
/// var settings = injector.Resolve<ISettingsManager>();
|
||||
/// return new PlayerService(logger, settings);
|
||||
/// });
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
IInjector RegisterFactory<TInterface>(Func<IInjector, TInterface> factory, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resolution - 서비스 해결
|
||||
|
||||
/// <summary>
|
||||
/// 등록된 서비스를 해결(Resolve)합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">서비스 타입 (인터페이스 또는 구체 클래스)</typeparam>
|
||||
/// <returns>서비스 인스턴스</returns>
|
||||
/// <exception cref="InvalidOperationException">서비스가 등록되지 않은 경우</exception>
|
||||
/// <exception cref="InvalidOperationException">순환 의존성이 감지된 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>등록된 서비스 타입에 따라 인스턴스를 생성하거나 캐시된 인스턴스를 반환합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 라이프사이클별 동작 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>App/Scene: 첫 호출 시 생성 후 캐싱, 이후 캐시된 인스턴스 반환</description></item>
|
||||
/// <item><description>Transient: 매 호출 시 새 인스턴스 생성</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 서비스 해결
|
||||
/// var logger = Injector.Resolve<ILogService>();
|
||||
/// logger.Log("Hello!");
|
||||
///
|
||||
/// // 또는 InjectorAppContext를 통해
|
||||
/// var logger = InjectorAppContext.Instance.Get<ILogService>();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
T Resolve<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 등록된 서비스를 해결합니다 (non-generic 버전).
|
||||
/// </summary>
|
||||
/// <param name="type">서비스 타입</param>
|
||||
/// <returns>서비스 인스턴스</returns>
|
||||
/// <exception cref="InvalidOperationException">서비스가 등록되지 않은 경우</exception>
|
||||
/// <exception cref="InvalidOperationException">순환 의존성이 감지된 경우</exception>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>리플렉션이나 동적 타입 처리가 필요한 경우 사용합니다.</para>
|
||||
/// <para>반환값을 적절한 타입으로 캐스팅해야 합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// Type serviceType = typeof(ILogService);
|
||||
/// var logger = (ILogService)Injector.Resolve(serviceType);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
object Resolve(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 해결을 시도합니다. 실패 시 null을 반환합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">서비스 타입</typeparam>
|
||||
/// <returns>서비스 인스턴스 또는 null</returns>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>서비스가 등록되지 않았거나 해결에 실패한 경우 예외 대신 null을 반환합니다.</para>
|
||||
/// <para>선택적 의존성을 처리할 때 유용합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 선택적으로 서비스 사용
|
||||
/// var analytics = Injector.TryResolve<IAnalyticsService>();
|
||||
/// analytics?.TrackEvent("game_start");
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
T TryResolve<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 해결을 시도합니다. 실패 시 null을 반환합니다 (non-generic 버전).
|
||||
/// </summary>
|
||||
/// <param name="type">서비스 타입</param>
|
||||
/// <returns>서비스 인스턴스 또는 null</returns>
|
||||
/// <remarks>
|
||||
/// <para>리플렉션이나 동적 타입 처리가 필요한 경우 사용합니다.</para>
|
||||
/// </remarks>
|
||||
object TryResolve(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// 특정 타입이 등록되어 있는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">확인할 타입</typeparam>
|
||||
/// <returns>등록되어 있으면 true, 아니면 false</returns>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// if (Injector.IsRegistered<IAnalyticsService>())
|
||||
/// {
|
||||
/// var analytics = Injector.Resolve<IAnalyticsService>();
|
||||
/// analytics.TrackEvent("event");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
bool IsRegistered<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 특정 타입이 등록되어 있는지 확인합니다 (non-generic 버전).
|
||||
/// </summary>
|
||||
/// <param name="type">확인할 타입</param>
|
||||
/// <returns>등록되어 있으면 true, 아니면 false</returns>
|
||||
bool IsRegistered(Type type);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Injection - 의존성 주입
|
||||
|
||||
/// <summary>
|
||||
/// 대상 객체의 [Inject] 어트리뷰트가 붙은 필드/프로퍼티에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">주입 대상 객체 (null인 경우 무시)</param>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>리플렉션을 사용하여 [Inject] 어트리뷰트가 붙은 모든 필드와 프로퍼티를 찾아 의존성을 주입합니다.</para>
|
||||
/// <para>상속 관계의 부모 클래스 멤버도 포함됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 주입 순서 ]</b></para>
|
||||
/// <list type="number">
|
||||
/// <item><description>필드(Field) 주입</description></item>
|
||||
/// <item><description>프로퍼티(Property) 주입</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 성능 최적화 ]</b></para>
|
||||
/// <para>리플렉션 결과가 캐싱되어 동일 타입에 대한 반복 주입 시 성능이 향상됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 순수 C# 객체에 의존성 주입
|
||||
/// var service = new MyService();
|
||||
/// Injector.Inject(service);
|
||||
///
|
||||
/// // MonoBehaviour에 수동 주입
|
||||
/// var player = GetComponent<PlayerController>();
|
||||
/// Injector.Inject(player);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
void Inject(object target);
|
||||
|
||||
/// <summary>
|
||||
/// GameObject의 모든 컴포넌트에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">주입 대상 GameObject (null인 경우 무시)</param>
|
||||
/// <param name="includeChildren">자식 오브젝트의 컴포넌트도 포함할지 여부 (기본값: true)</param>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>GameObject에 붙은 모든 MonoBehaviour 컴포넌트에 대해 Inject()를 호출합니다.</para>
|
||||
///
|
||||
/// <para><b>[ includeChildren 옵션 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>true: GetComponentsInChildren으로 모든 자식의 컴포넌트도 포함</description></item>
|
||||
/// <item><description>false: GetComponents로 해당 GameObject의 컴포넌트만 포함</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 사례 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Instantiate로 동적 생성된 객체에 의존성 주입</description></item>
|
||||
/// <item><description>씬에 배치된 객체에 수동으로 의존성 주입</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Instantiate 후 의존성 주입
|
||||
/// var enemy = Instantiate(enemyPrefab);
|
||||
/// Injector.InjectGameObject(enemy, true);
|
||||
///
|
||||
/// // InjectorSceneContext를 통해
|
||||
/// InjectorSceneContext.Instance.InjectInstantiated(enemy);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
void InjectGameObject(GameObject gameObject, bool includeChildren = true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle - 라이프사이클 관리
|
||||
|
||||
/// <summary>
|
||||
/// 씬 언로드 시 Scene 라이프사이클 서비스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>ClearServices(ServiceLifetime.Scene)을 호출하여 Scene 라이프사이클 서비스들을 정리합니다.</para>
|
||||
/// <para>InjectorSceneContext.OnDestroy()에서 자동으로 호출됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 주의사항 ]</b></para>
|
||||
/// <para>일반적으로 직접 호출할 필요 없이 InjectorSceneContext가 자동으로 처리합니다.</para>
|
||||
/// </remarks>
|
||||
void OnSceneUnloaded();
|
||||
|
||||
/// <summary>
|
||||
/// 특정 라이프사이클의 서비스들을 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="lifetime">정리할 라이프사이클</param>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <para>지정된 라이프사이클의 캐시된 인스턴스들을 정리합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 정리 방식 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>MonoBehaviour: GameObject.Destroy() 호출 (Singleton 제외)</description></item>
|
||||
/// <item><description>IDisposable: Dispose() 호출</description></item>
|
||||
/// <item><description>기타: 캐시에서 참조 제거</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Scene 서비스 정리
|
||||
/// Injector.ClearServices(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // 모든 서비스 정리 (앱 종료 시)
|
||||
/// Injector.ClearServices(ServiceLifetime.App);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
void ClearServices(ServiceLifetime lifetime);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/core/Injector/IInjector.cs.meta
Normal file
2
Assets/Scripts/UVC/core/Injector/IInjector.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 940de3ad3cfdae94da748874d9bbbee8
|
||||
902
Assets/Scripts/UVC/core/Injector/Injector.cs
Normal file
902
Assets/Scripts/UVC/core/Injector/Injector.cs
Normal file
@@ -0,0 +1,902 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 경량 의존성 주입(DI) 컨테이너 구현체
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>Unity 환경에 최적화된 경량 DI 컨테이너입니다.</para>
|
||||
/// <para>4가지 서비스 타입과 3가지 라이프사이클을 지원합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 지원하는 4가지 타입 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>타입</term>
|
||||
/// <description>설명</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>Type A (PureCSharp)</term>
|
||||
/// <description>순수 C# 클래스 - new T()로 생성</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type B (MonoBehaviour)</term>
|
||||
/// <description>런타임 GameObject 생성 후 AddComponent</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type C (Prefab)</term>
|
||||
/// <description>등록된 Prefab을 Instantiate</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type D (Singleton)</term>
|
||||
/// <description>기존 Singleton<T>.Instance 연동</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 지원하는 3가지 라이프사이클 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>App:</b> 애플리케이션 전체 수명 (DontDestroyOnLoad)</description></item>
|
||||
/// <item><description><b>Scene:</b> 현재 씬 수명 (씬 전환 시 파괴)</description></item>
|
||||
/// <item><description><b>Transient:</b> 매번 새 인스턴스 생성</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 주요 기능 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>필드/프로퍼티 의존성 주입 ([Inject] 어트리뷰트)</description></item>
|
||||
/// <item><description>순환 의존성 감지</description></item>
|
||||
/// <item><description>리플렉션 캐싱을 통한 성능 최적화</description></item>
|
||||
/// <item><description>MonoBehaviour 파괴 감지 및 재생성</description></item>
|
||||
/// <item><description>IDisposable 자동 정리</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 - 서비스 등록 ]</b></para>
|
||||
/// <code>
|
||||
/// // InjectorAppContext에서 서비스 등록
|
||||
/// protected override void RegisterServices()
|
||||
/// {
|
||||
/// base.RegisterServices();
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
/// Injector.RegisterSingleton<SettingsManager>();
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 - 의존성 주입 (방법 1: [Inject] 어트리뷰트) ]</b></para>
|
||||
/// <code>
|
||||
/// public class PlayerController : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger;
|
||||
/// [Inject] private IAudioManager _audio;
|
||||
///
|
||||
/// private void Start()
|
||||
/// {
|
||||
/// _logger.Log("Player initialized");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 - 의존성 주입 (방법 2: 코드에서 직접 접근) ]</b></para>
|
||||
/// <code>
|
||||
/// public class GameManager : MonoBehaviour
|
||||
/// {
|
||||
/// private ILogService _logger;
|
||||
/// private IAudioManager _audio;
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// // InjectorAppContext를 통한 접근
|
||||
/// _logger = InjectorAppContext.Instance.Get<ILogService>();
|
||||
/// _audio = InjectorAppContext.Instance.Get<IAudioManager>();
|
||||
///
|
||||
/// // 또는 Injector 직접 접근
|
||||
/// var injector = InjectorAppContext.Instance.Injector;
|
||||
/// _logger = injector.Resolve<ILogService>();
|
||||
///
|
||||
/// // 안전한 접근 (없으면 null 반환)
|
||||
/// var analytics = InjectorAppContext.Instance.TryGet<IAnalyticsService>();
|
||||
/// analytics?.TrackEvent("game_start");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 - 동적 생성 객체에 주입 ]</b></para>
|
||||
/// <code>
|
||||
/// // Instantiate 후 의존성 주입
|
||||
/// var enemy = Instantiate(enemyPrefab);
|
||||
/// InjectorAppContext.Instance.InjectInto(enemy);
|
||||
///
|
||||
/// // 또는 InjectorSceneContext 사용
|
||||
/// var sceneContext = FindObjectOfType<InjectorSceneContext>();
|
||||
/// sceneContext.InjectInstantiated(enemy);
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 구조 ]</b></para>
|
||||
/// <code>
|
||||
/// Injector
|
||||
/// ├── _services: Dictionary<Type, ServiceDescriptor> // 등록된 서비스들
|
||||
/// ├── _resolvingTypes: HashSet<Type> // 순환 의존성 감지용
|
||||
/// ├── _fieldCache: Dictionary<Type, FieldInfo[]> // 리플렉션 캐시
|
||||
/// └── _propertyCache: Dictionary<Type, PropertyInfo[]> // 리플렉션 캐시
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 등록 순서와 인스턴스 생성 시점 ]</b></para>
|
||||
/// <para>Register와 Resolve는 별개의 작업입니다:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>Register:</b> 서비스 메타데이터만 등록 (인스턴스 생성 안 함)</description></item>
|
||||
/// <item><description><b>Resolve:</b> 처음 호출될 때 인스턴스 생성 (Lazy Instantiation)</description></item>
|
||||
/// </list>
|
||||
/// <para>따라서 등록 순서는 중요하지 않으며, Resolve 시점에 의존성이 등록되어 있기만 하면 됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ Unity 실행 순서와 의존성 접근 ]</b></para>
|
||||
/// <code>
|
||||
/// [실행 순서 타임라인]
|
||||
/// ┌─────────────────────────────────────────────────────────────────────┐
|
||||
/// │ InjectorAppContext.Awake (-1000) │
|
||||
/// │ └→ Injector 생성 + App 서비스 메타데이터 등록 │
|
||||
/// ├─────────────────────────────────────────────────────────────────────┤
|
||||
/// │ InjectorSceneContext.Awake (-900) │
|
||||
/// │ └→ Scene 서비스 메타데이터 등록 │
|
||||
/// │ └→ 씬 오브젝트들에 의존성 주입 (InjectGameObject) │
|
||||
/// │ └→ 이 시점에 [Inject] 필드에 Resolve하여 인스턴스 할당 │
|
||||
/// ├─────────────────────────────────────────────────────────────────────┤
|
||||
/// │ 일반 MonoBehaviour.Awake (0) │
|
||||
/// │ └→ [Inject] 필드에 이미 인스턴스가 주입된 상태 │
|
||||
/// │ └→ ⚠ 주의: 다른 MonoBehaviour의 Awake 실행 순서는 보장 안 됨 │
|
||||
/// ├─────────────────────────────────────────────────────────────────────┤
|
||||
/// │ 일반 MonoBehaviour.Start │
|
||||
/// │ └→ ✅ 안전: 모든 Awake 완료 후 실행, 모든 의존성 사용 가능 │
|
||||
/// └─────────────────────────────────────────────────────────────────────┘
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 의존성 접근 권장 시점 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>메서드</term>
|
||||
/// <description>안전성</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>Awake()</term>
|
||||
/// <description>⚠ 주의 필요 - [Inject] 필드는 사용 가능하나, 다른 컴포넌트의 Awake 실행 보장 안 됨</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>OnEnable()</term>
|
||||
/// <description>⚠ 주의 필요 - Awake와 동일한 제약</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Start()</term>
|
||||
/// <description>✅ 안전 - 모든 Awake/OnEnable 완료 후 실행, 의존성 사용 권장</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Update() 이후</term>
|
||||
/// <description>✅ 안전 - 초기화 완료된 상태</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ Awake에서 안전하게 사용하는 방법 ]</b></para>
|
||||
/// <code>
|
||||
/// public class MyComponent : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger; // InjectorSceneContext가 주입
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// // ✅ [Inject] 필드는 이미 주입된 상태 (InjectorSceneContext.Awake에서)
|
||||
/// _logger?.Log("MyComponent Awake");
|
||||
///
|
||||
/// // ⚠ 다른 MonoBehaviour 참조는 null일 수 있음
|
||||
/// // var other = FindObjectOfType<OtherComponent>();
|
||||
/// // other.DoSomething(); // 위험: OtherComponent.Awake가 아직 실행 안 됐을 수 있음
|
||||
/// }
|
||||
///
|
||||
/// private void Start()
|
||||
/// {
|
||||
/// // ✅ Start에서는 모든 Awake가 완료되어 안전
|
||||
/// var other = FindObjectOfType<OtherComponent>();
|
||||
/// other.DoSomething(); // 안전
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ Awake에서 반드시 의존성이 필요한 경우 해결 방법 ]</b></para>
|
||||
///
|
||||
/// <para><b>방법 1: DefaultExecutionOrder로 실행 순서 지정</b></para>
|
||||
/// <para>가장 권장되는 방법입니다. 컴포넌트의 실행 순서를 명시적으로 지정합니다.</para>
|
||||
/// <code>
|
||||
/// // B가 A보다 먼저 초기화되어야 함
|
||||
/// [DefaultExecutionOrder(-100)] // InjectorSceneContext(-900) 이후, 일반(0) 이전
|
||||
/// public class ServiceB : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger;
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// _logger?.Log("ServiceB initialized first");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// [DefaultExecutionOrder(-50)] // ServiceB(-100) 이후
|
||||
/// public class ServiceA : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ServiceB _serviceB; // 이미 초기화된 상태
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// _serviceB.DoSomething(); // ✅ 안전
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>방법 2: InjectorAppContext.Instance.Get으로 직접 접근</b></para>
|
||||
/// <para>[Inject] 대신 코드에서 직접 서비스를 가져옵니다.</para>
|
||||
/// <code>
|
||||
/// public class MyComponent : MonoBehaviour
|
||||
/// {
|
||||
/// private ILogService _logger;
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// // InjectorAppContext는 -1000 순서로 이미 초기화됨
|
||||
/// _logger = InjectorAppContext.Instance.Get<ILogService>();
|
||||
/// _logger.Log("Direct access in Awake");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>방법 3: Lazy 초기화 패턴</b></para>
|
||||
/// <para>첫 접근 시점에 서비스를 가져옵니다.</para>
|
||||
/// <code>
|
||||
/// public class MyComponent : MonoBehaviour
|
||||
/// {
|
||||
/// private ILogService _logger;
|
||||
/// private ILogService Logger => _logger ??= InjectorAppContext.Instance.Get<ILogService>();
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// Logger.Log("Lazy access"); // 첫 접근 시 초기화
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>방법 4: 초기화 지연 (코루틴)</b></para>
|
||||
/// <para>특정 조건이 충족될 때까지 대기합니다.</para>
|
||||
/// <code>
|
||||
/// public class MyComponent : MonoBehaviour
|
||||
/// {
|
||||
/// [Inject] private ILogService _logger;
|
||||
/// [Inject] private IOtherService _other;
|
||||
///
|
||||
/// private void Awake()
|
||||
/// {
|
||||
/// StartCoroutine(InitializeWhenReady());
|
||||
/// }
|
||||
///
|
||||
/// private IEnumerator InitializeWhenReady()
|
||||
/// {
|
||||
/// // 다른 서비스가 준비될 때까지 대기
|
||||
/// while (_other == null || !_other.IsReady)
|
||||
/// {
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// _logger.Log("All dependencies ready");
|
||||
/// PerformInitialization();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 권장 순서 ]</b></para>
|
||||
/// <list type="number">
|
||||
/// <item><description>Start() 사용 (가장 단순하고 안전)</description></item>
|
||||
/// <item><description>DefaultExecutionOrder (명시적 순서 필요 시)</description></item>
|
||||
/// <item><description>직접 접근 또는 Lazy 패턴 (Awake 필수인 경우)</description></item>
|
||||
/// <item><description>코루틴 대기 (복잡한 초기화 의존성)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ RegisterSingleton vs RegisterInstance 사용 가이드 ]</b></para>
|
||||
/// <para>Singleton 타입(Singleton<T>, SingletonApp<T>, SingletonScene<T>)을 등록할 때는</para>
|
||||
/// <para>항상 <b>RegisterSingleton<T>()</b>를 사용하세요.</para>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 비교 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>상황</term>
|
||||
/// <description>등록 방법</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>Singleton<T> (순수 C#)</term>
|
||||
/// <description>RegisterSingleton<T>() - Instance 접근 시 자동 생성</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>SingletonApp<T> (씬에 미리 배치)</term>
|
||||
/// <description>RegisterSingleton<T>() - Instance가 FindFirstObjectByType으로 찾음</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>SingletonApp<T> (자동 생성)</term>
|
||||
/// <description>RegisterSingleton<T>() - Instance 접근 시 자동 생성</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>SingletonScene<T> (씬에 미리 배치)</term>
|
||||
/// <description>RegisterSingleton<T>() - Instance가 FindFirstObjectByType으로 찾음</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>SingletonScene<T> (자동 생성)</term>
|
||||
/// <description>RegisterSingleton<T>() - Instance 접근 시 자동 생성</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>일반 MonoBehaviour (씬에 미리 배치)</term>
|
||||
/// <description>RegisterInstance<T>(instance) - 직접 인스턴스 전달 필요</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ SingletonScene을 씬에 미리 배치한 경우 ]</b></para>
|
||||
/// <para>SingletonScene<T>.Instance 프로퍼티는 내부적으로 FindFirstObjectByType을 사용하여</para>
|
||||
/// <para>씬에 이미 존재하는 인스턴스를 자동으로 찾습니다. 따라서 씬에 미리 배치해도</para>
|
||||
/// <para>RegisterSingleton<T>()만 호출하면 됩니다.</para>
|
||||
///
|
||||
/// <code>
|
||||
/// // SingletonScene.Instance 내부 동작
|
||||
/// public static T Instance
|
||||
/// {
|
||||
/// get
|
||||
/// {
|
||||
/// if (instance == null)
|
||||
/// {
|
||||
/// // 씬에서 기존 인스턴스 검색
|
||||
/// instance = (T)FindFirstObjectByType(typeof(T));
|
||||
/// if (instance == null)
|
||||
/// {
|
||||
/// // 없으면 새로 생성
|
||||
/// instance = new GameObject(typeof(T).Name).AddComponent<T>();
|
||||
/// }
|
||||
/// }
|
||||
/// return instance;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // ✅ 올바른 사용법 - 모든 Singleton 타입
|
||||
/// Injector.RegisterSingleton<MySingletonClass>(); // 순수 C# Singleton
|
||||
/// Injector.RegisterSingleton<MyAppSingleton>(); // SingletonApp (씬 배치 or 자동 생성)
|
||||
/// Injector.RegisterSingleton<MySceneSingleton>(); // SingletonScene (씬 배치 or 자동 생성)
|
||||
///
|
||||
/// // ✅ 올바른 사용법 - 일반 MonoBehaviour (씬에 미리 배치)
|
||||
/// [SerializeField] private MyManager myManager;
|
||||
/// Injector.RegisterInstance<IMyManager>(myManager);
|
||||
///
|
||||
/// // ❌ 잘못된 사용법 - Singleton 타입에 RegisterInstance 사용
|
||||
/// // Injector.RegisterInstance<MySceneSingleton>(FindObjectOfType<MySceneSingleton>());
|
||||
/// // → RegisterSingleton이 자동으로 처리하므로 불필요
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ RegisterInstance를 사용해야 하는 경우 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Singleton 패턴을 사용하지 않는 일반 MonoBehaviour를 씬에 미리 배치한 경우</description></item>
|
||||
/// <item><description>인터페이스로 등록해야 하는 경우 (예: RegisterInstance<IService>(concreteInstance))</description></item>
|
||||
/// <item><description>외부에서 생성된 인스턴스를 등록해야 하는 경우</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IInjector"/>
|
||||
/// <seealso cref="ServiceDescriptor"/>
|
||||
/// <seealso cref="InjectAttribute"/>
|
||||
/// <seealso cref="InjectorAppContext"/>
|
||||
/// <seealso cref="InjectorSceneContext"/>
|
||||
public class Injector : IInjector
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>등록된 서비스들의 메타데이터 저장소 (키: 인터페이스 타입)</summary>
|
||||
private readonly Dictionary<Type, ServiceDescriptor> _services = new();
|
||||
|
||||
/// <summary>순환 의존성 감지를 위한 현재 해결 중인 타입 집합</summary>
|
||||
private readonly HashSet<Type> _resolvingTypes = new();
|
||||
|
||||
/// <summary>Dispose 호출 여부</summary>
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>[Inject] 필드 캐시 - 동일 타입 반복 주입 시 성능 향상</summary>
|
||||
private static readonly Dictionary<Type, FieldInfo[]> _fieldCache = new();
|
||||
|
||||
/// <summary>[Inject] 프로퍼티 캐시 - 동일 타입 반복 주입 시 성능 향상</summary>
|
||||
private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new();
|
||||
|
||||
/// <summary>캐시 접근 동기화를 위한 락 객체</summary>
|
||||
private static readonly object _cacheLock = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registration
|
||||
|
||||
public IInjector Register<TInterface, TImplementation>(ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class
|
||||
where TImplementation : class, TInterface
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var descriptor = new ServiceDescriptor(typeof(TInterface), typeof(TImplementation), lifetime);
|
||||
_services[typeof(TInterface)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IInjector Register<T>(ServiceLifetime lifetime = ServiceLifetime.App) where T : class
|
||||
{
|
||||
return Register<T, T>(lifetime);
|
||||
}
|
||||
|
||||
public IInjector RegisterInstance<TInterface>(TInterface instance, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (instance == null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(TInterface), instance, lifetime);
|
||||
_services[typeof(TInterface)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IInjector RegisterPrefab<T>(T prefab, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where T : MonoBehaviour
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (prefab == null)
|
||||
throw new ArgumentNullException(nameof(prefab));
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(T), prefab.gameObject, lifetime);
|
||||
_services[typeof(T)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IInjector RegisterPrefab<T>(GameObject prefab, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (prefab == null)
|
||||
throw new ArgumentNullException(nameof(prefab));
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(T), prefab, lifetime);
|
||||
_services[typeof(T)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IInjector RegisterSingleton<T>() where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var descriptor = new ServiceDescriptor(typeof(T));
|
||||
_services[typeof(T)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IInjector RegisterFactory<TInterface>(Func<IInjector, TInterface> factory, ServiceLifetime lifetime = ServiceLifetime.App)
|
||||
where TInterface : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
var descriptor = new ServiceDescriptor(typeof(TInterface), injector => factory(injector), lifetime);
|
||||
_services[typeof(TInterface)] = descriptor;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Resolution
|
||||
|
||||
public T Resolve<T>() where T : class
|
||||
{
|
||||
return (T)Resolve(typeof(T));
|
||||
}
|
||||
|
||||
public object Resolve(Type type)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!_services.TryGetValue(type, out var descriptor))
|
||||
{
|
||||
throw new InvalidOperationException($"Service of type '{type.FullName}' is not registered.");
|
||||
}
|
||||
|
||||
return ResolveDescriptor(descriptor);
|
||||
}
|
||||
|
||||
public T TryResolve<T>() where T : class
|
||||
{
|
||||
return (T)TryResolve(typeof(T));
|
||||
}
|
||||
|
||||
public object TryResolve(Type type)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!_services.TryGetValue(type, out var descriptor))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return ResolveDescriptor(descriptor);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRegistered<T>() where T : class
|
||||
{
|
||||
return IsRegistered(typeof(T));
|
||||
}
|
||||
|
||||
public bool IsRegistered(Type type)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return _services.ContainsKey(type);
|
||||
}
|
||||
|
||||
private object ResolveDescriptor(ServiceDescriptor descriptor)
|
||||
{
|
||||
// Transient는 항상 새 인스턴스 생성
|
||||
if (descriptor.Lifetime == ServiceLifetime.Transient)
|
||||
{
|
||||
return CreateInstance(descriptor);
|
||||
}
|
||||
|
||||
// 캐시된 인스턴스가 있으면 반환
|
||||
if (descriptor.Instance != null)
|
||||
{
|
||||
// MonoBehaviour가 파괴되었는지 확인
|
||||
if (descriptor.Instance is UnityEngine.Object unityObj && unityObj == null)
|
||||
{
|
||||
descriptor.Instance = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return descriptor.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
// 새 인스턴스 생성 및 캐싱
|
||||
var instance = CreateInstance(descriptor);
|
||||
descriptor.Instance = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private object CreateInstance(ServiceDescriptor descriptor)
|
||||
{
|
||||
var type = descriptor.InterfaceType;
|
||||
|
||||
// 순환 의존성 체크
|
||||
if (_resolvingTypes.Contains(type))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Circular dependency detected while resolving '{type.FullName}'.");
|
||||
}
|
||||
|
||||
_resolvingTypes.Add(type);
|
||||
|
||||
try
|
||||
{
|
||||
object instance = descriptor.ServiceType switch
|
||||
{
|
||||
ServiceType.PureCSharp => CreatePureCSharpInstance(descriptor),
|
||||
ServiceType.MonoBehaviour => CreateMonoBehaviourInstance(descriptor),
|
||||
ServiceType.Prefab => CreatePrefabInstance(descriptor),
|
||||
ServiceType.Singleton => ResolveSingleton(descriptor),
|
||||
ServiceType.Instance => descriptor.Instance,
|
||||
_ => throw new InvalidOperationException($"Unknown service type: {descriptor.ServiceType}")
|
||||
};
|
||||
|
||||
return instance;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resolvingTypes.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
private object CreatePureCSharpInstance(ServiceDescriptor descriptor)
|
||||
{
|
||||
object instance;
|
||||
|
||||
if (descriptor.Factory != null)
|
||||
{
|
||||
instance = descriptor.Factory(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = Activator.CreateInstance(descriptor.ImplementationType);
|
||||
}
|
||||
|
||||
Inject(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private object CreateMonoBehaviourInstance(ServiceDescriptor descriptor)
|
||||
{
|
||||
var go = new GameObject($"[Injector] {descriptor.InterfaceType.Name}");
|
||||
var instance = go.AddComponent(descriptor.ImplementationType);
|
||||
|
||||
if (descriptor.Lifetime == ServiceLifetime.App)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
Inject(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private object CreatePrefabInstance(ServiceDescriptor descriptor)
|
||||
{
|
||||
var go = UnityEngine.Object.Instantiate(descriptor.PrefabSource);
|
||||
go.name = $"[Injector] {descriptor.InterfaceType.Name}";
|
||||
|
||||
if (descriptor.Lifetime == ServiceLifetime.App)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
}
|
||||
|
||||
var component = go.GetComponent(descriptor.InterfaceType);
|
||||
if (component == null)
|
||||
{
|
||||
component = go.GetComponent(descriptor.ImplementationType);
|
||||
}
|
||||
|
||||
InjectGameObject(go, true);
|
||||
return component;
|
||||
}
|
||||
|
||||
private object ResolveSingleton(ServiceDescriptor descriptor)
|
||||
{
|
||||
var type = descriptor.ImplementationType;
|
||||
|
||||
// Instance 프로퍼티 찾기
|
||||
var instanceProperty = type.GetProperty("Instance",
|
||||
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||
|
||||
if (instanceProperty != null)
|
||||
{
|
||||
return instanceProperty.GetValue(null);
|
||||
}
|
||||
|
||||
// Singleton<T>.Instance 패턴 시도
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
if (baseType.IsGenericType)
|
||||
{
|
||||
var genericDef = baseType.GetGenericTypeDefinition();
|
||||
if (genericDef.Name.StartsWith("Singleton"))
|
||||
{
|
||||
instanceProperty = baseType.GetProperty("Instance",
|
||||
BindingFlags.Public | BindingFlags.Static);
|
||||
if (instanceProperty != null)
|
||||
{
|
||||
return instanceProperty.GetValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot resolve singleton '{type.FullName}'. No 'Instance' property found.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Injection
|
||||
|
||||
public void Inject(object target)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
var type = target.GetType();
|
||||
|
||||
// 필드 주입
|
||||
var fields = GetInjectableFields(type);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var attr = field.GetCustomAttribute<InjectAttribute>();
|
||||
var value = attr.Optional ? TryResolve(field.FieldType) : Resolve(field.FieldType);
|
||||
|
||||
if (value != null || !attr.Optional)
|
||||
{
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 프로퍼티 주입
|
||||
var properties = GetInjectableProperties(type);
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var attr = property.GetCustomAttribute<InjectAttribute>();
|
||||
var value = attr.Optional ? TryResolve(property.PropertyType) : Resolve(property.PropertyType);
|
||||
|
||||
if (value != null || !attr.Optional)
|
||||
{
|
||||
property.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InjectGameObject(GameObject gameObject, bool includeChildren = true)
|
||||
{
|
||||
if (gameObject == null) return;
|
||||
|
||||
if (includeChildren)
|
||||
{
|
||||
var components = gameObject.GetComponentsInChildren<MonoBehaviour>(true);
|
||||
foreach (var component in components)
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
Inject(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var components = gameObject.GetComponents<MonoBehaviour>();
|
||||
foreach (var component in components)
|
||||
{
|
||||
if (component != null)
|
||||
{
|
||||
Inject(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FieldInfo[] GetInjectableFields(Type type)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (!_fieldCache.TryGetValue(type, out var fields))
|
||||
{
|
||||
var fieldList = new List<FieldInfo>();
|
||||
var currentType = type;
|
||||
|
||||
while (currentType != null && currentType != typeof(object))
|
||||
{
|
||||
var typeFields = currentType.GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(f => f.GetCustomAttribute<InjectAttribute>() != null);
|
||||
|
||||
fieldList.AddRange(typeFields);
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
fields = fieldList.ToArray();
|
||||
_fieldCache[type] = fields;
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyInfo[] GetInjectableProperties(Type type)
|
||||
{
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (!_propertyCache.TryGetValue(type, out var properties))
|
||||
{
|
||||
var propertyList = new List<PropertyInfo>();
|
||||
var currentType = type;
|
||||
|
||||
while (currentType != null && currentType != typeof(object))
|
||||
{
|
||||
var typeProperties = currentType.GetProperties(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null && p.CanWrite);
|
||||
|
||||
propertyList.AddRange(typeProperties);
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
properties = propertyList.ToArray();
|
||||
_propertyCache[type] = properties;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
public void OnSceneUnloaded()
|
||||
{
|
||||
ClearServices(ServiceLifetime.Scene);
|
||||
}
|
||||
|
||||
public void ClearServices(ServiceLifetime lifetime)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var servicesToClear = _services.Values
|
||||
.Where(s => s.Lifetime == lifetime && s.Instance != null)
|
||||
.ToList();
|
||||
|
||||
foreach (var service in servicesToClear)
|
||||
{
|
||||
DisposeInstance(service.Instance);
|
||||
service.Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeInstance(object instance)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
// MonoBehaviour인 경우 GameObject 파괴
|
||||
if (instance is MonoBehaviour mb && mb != null)
|
||||
{
|
||||
// Singleton 타입은 파괴하지 않음 (자체 라이프사이클 관리)
|
||||
var type = instance.GetType();
|
||||
if (!IsSingletonType(type))
|
||||
{
|
||||
UnityEngine.Object.Destroy(mb.gameObject);
|
||||
}
|
||||
}
|
||||
// IDisposable 구현 시 Dispose 호출
|
||||
else if (instance is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSingletonType(Type type)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
if (baseType.IsGenericType)
|
||||
{
|
||||
var name = baseType.GetGenericTypeDefinition().Name;
|
||||
if (name.StartsWith("Singleton"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
// Scene 서비스 먼저 정리
|
||||
ClearServices(ServiceLifetime.Scene);
|
||||
|
||||
// App 서비스 정리
|
||||
var appServices = _services.Values
|
||||
.Where(s => s.Lifetime == ServiceLifetime.App && s.Instance != null)
|
||||
.ToList();
|
||||
|
||||
foreach (var service in appServices)
|
||||
{
|
||||
DisposeInstance(service.Instance);
|
||||
}
|
||||
|
||||
_services.Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Injector));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/core/Injector/Injector.cs.meta
Normal file
2
Assets/Scripts/UVC/core/Injector/Injector.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c966a2569148a3343bcf0e1fc501ef6b
|
||||
323
Assets/Scripts/UVC/core/Injector/InjectorAppContext.cs
Normal file
323
Assets/Scripts/UVC/core/Injector/InjectorAppContext.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 앱 레벨 Injector 컨텍스트 - DI 시스템의 진입점
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>애플리케이션 전체 수명 동안 유지되는 서비스들을 등록하고 관리하는 부트스트래퍼입니다.</para>
|
||||
/// <para>SingletonApp<T>를 상속받아 씬 전환 시에도 유지됩니다 (DontDestroyOnLoad).</para>
|
||||
///
|
||||
/// <para><b>[ 실행 순서 ]</b></para>
|
||||
/// <para>DefaultExecutionOrder(-1000)으로 다른 MonoBehaviour보다 먼저 초기화됩니다.</para>
|
||||
/// <para>InjectorSceneContext(-900)보다 먼저 실행되어 App 서비스가 먼저 등록됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 역할 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Injector 인스턴스 생성 및 관리</description></item>
|
||||
/// <item><description>App 라이프사이클 서비스 등록</description></item>
|
||||
/// <item><description>씬 로드 시 자동 의존성 주입 (선택적)</description></item>
|
||||
/// <item><description>서비스 해결을 위한 편의 메서드 제공</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ Unity Editor 설정 ]</b></para>
|
||||
/// <list type="number">
|
||||
/// <item><description>첫 씬에 빈 GameObject 생성 → 이름: "AppContext"</description></item>
|
||||
/// <item><description>InjectorAppContext (또는 상속받은 클래스) 컴포넌트 추가</description></item>
|
||||
/// <item><description>Inspector에서 App Prefab 목록 설정 (선택)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 방법 - 상속 ]</b></para>
|
||||
/// <code>
|
||||
/// public class MyAppContext : InjectorAppContext
|
||||
/// {
|
||||
/// [SerializeField] private UIManager uiManagerPrefab;
|
||||
///
|
||||
/// protected override void RegisterServices()
|
||||
/// {
|
||||
/// base.RegisterServices();
|
||||
///
|
||||
/// // Type A: 순수 C# 클래스
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type B: MonoBehaviour 동적 생성
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type C: Prefab 기반
|
||||
/// Injector.RegisterPrefab<IUIManager>(uiManagerPrefab.gameObject, ServiceLifetime.App);
|
||||
///
|
||||
/// // Type D: Singleton 연동
|
||||
/// Injector.RegisterSingleton<SettingsManager>();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 서비스 접근 방법 ]</b></para>
|
||||
/// <code>
|
||||
/// // 방법 1: [Inject] 어트리뷰트 (권장)
|
||||
/// [Inject] private ILogService _logger;
|
||||
///
|
||||
/// // 방법 2: InjectorAppContext를 통한 접근
|
||||
/// var logger = InjectorAppContext.Instance.Get<ILogService>();
|
||||
///
|
||||
/// // 방법 3: Injector 직접 접근
|
||||
/// var logger = InjectorAppContext.Instance.Injector.Resolve<ILogService>();
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 이벤트 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>OnInjectorInitialized: Injector 생성 직후 발생</description></item>
|
||||
/// <item><description>OnServicesRegistered: 서비스 등록 완료 후 발생</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="InjectorSceneContext"/>
|
||||
/// <seealso cref="ServiceLifetime"/>
|
||||
[DefaultExecutionOrder(-1000)]
|
||||
public class InjectorAppContext : SingletonApp<InjectorAppContext>
|
||||
{
|
||||
#region Inspector Fields
|
||||
|
||||
[Header("App Lifetime Prefabs")]
|
||||
[Tooltip("앱 전체 수명 동안 유지될 Prefab들")]
|
||||
[SerializeField]
|
||||
private List<MonoBehaviour> appPrefabs = new();
|
||||
|
||||
[Header("Settings")]
|
||||
[Tooltip("씬의 모든 MonoBehaviour에 자동으로 의존성 주입 수행")]
|
||||
[SerializeField]
|
||||
private bool autoInjectOnSceneLoad = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 전역 Injector 인스턴스
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>모든 서비스 등록 및 해결에 사용되는 DI 컨테이너입니다.</para>
|
||||
/// <para>Init() 메서드에서 생성되며, OnDestroy()에서 Dispose됩니다.</para>
|
||||
/// </remarks>
|
||||
public IInjector Injector { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 초기화 완료 여부
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Init() 완료 후 true가 됩니다.</para>
|
||||
/// <para>InjectorSceneContext는 이 값이 true가 될 때까지 대기합니다.</para>
|
||||
/// </remarks>
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Injector 초기화 완료 시 발생
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Injector 인스턴스가 생성되고 서비스 등록이 완료된 직후 발생합니다.</para>
|
||||
/// </remarks>
|
||||
public event System.Action OnInjectorInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 등록 완료 시 발생
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>RegisterServices() 메서드 완료 후 발생합니다.</para>
|
||||
/// <para>이 이벤트 이후부터 등록된 서비스들을 Resolve할 수 있습니다.</para>
|
||||
/// </remarks>
|
||||
public event System.Action OnServicesRegistered;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
// Injector 인스턴스 생성
|
||||
Injector = new Injector();
|
||||
|
||||
// 서비스 등록
|
||||
RegisterServices();
|
||||
|
||||
// 씬 로드 이벤트 구독
|
||||
if (autoInjectOnSceneLoad)
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
IsInitialized = true;
|
||||
OnInjectorInitialized?.Invoke();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (autoInjectOnSceneLoad)
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
Injector?.Dispose();
|
||||
Injector = null;
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Registration
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 등록을 수행합니다. 자식 클래스에서 오버라이드하여 추가 서비스를 등록하세요.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 호출 시점 ]</b></para>
|
||||
/// <para>Init() 메서드에서 Injector 인스턴스 생성 직후 호출됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 오버라이드 시 주의사항 ]</b></para>
|
||||
/// <para>base.RegisterServices()를 먼저 호출하여 Inspector에서 할당한 Prefab들이 등록되도록 하세요.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// protected override void RegisterServices()
|
||||
/// {
|
||||
/// base.RegisterServices(); // Inspector Prefab 등록
|
||||
///
|
||||
/// // Type A: 순수 C# 클래스
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type B: MonoBehaviour 동적 생성
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type D: Singleton 연동
|
||||
/// Injector.RegisterSingleton<SettingsManager>();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
protected virtual void RegisterServices()
|
||||
{
|
||||
// Inspector에서 할당된 App Prefab 등록
|
||||
foreach (var prefab in appPrefabs)
|
||||
{
|
||||
if (prefab != null)
|
||||
{
|
||||
RegisterPrefabByType(prefab, ServiceLifetime.App);
|
||||
}
|
||||
}
|
||||
|
||||
// 자기 자신 등록
|
||||
Injector.RegisterInstance<InjectorAppContext>(this, ServiceLifetime.App);
|
||||
|
||||
OnServicesRegistered?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefab을 해당 타입으로 등록합니다.
|
||||
/// </summary>
|
||||
private void RegisterPrefabByType(MonoBehaviour prefab, ServiceLifetime lifetime)
|
||||
{
|
||||
var type = prefab.GetType();
|
||||
|
||||
// 리플렉션을 사용하여 RegisterPrefab<T> 호출
|
||||
var method = typeof(IInjector).GetMethod("RegisterPrefab", new[] { typeof(GameObject), typeof(ServiceLifetime) });
|
||||
var genericMethod = method.MakeGenericMethod(type);
|
||||
genericMethod.Invoke(Injector, new object[] { prefab.gameObject, lifetime });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scene Events
|
||||
|
||||
private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
|
||||
{
|
||||
if (!autoInjectOnSceneLoad) return;
|
||||
|
||||
// 씬의 루트 오브젝트들에 의존성 주입
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
foreach (var rootObj in rootObjects)
|
||||
{
|
||||
Injector.InjectGameObject(rootObj, true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// 수동으로 특정 GameObject에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">주입 대상 GameObject</param>
|
||||
/// <param name="includeChildren">자식 오브젝트 포함 여부 (기본값: true)</param>
|
||||
/// <remarks>
|
||||
/// <para>Instantiate로 동적 생성된 객체에 의존성을 주입할 때 사용합니다.</para>
|
||||
/// <code>
|
||||
/// var enemy = Instantiate(enemyPrefab);
|
||||
/// InjectorAppContext.Instance.InjectInto(enemy);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public void InjectInto(GameObject target, bool includeChildren = true)
|
||||
{
|
||||
Injector?.InjectGameObject(target, includeChildren);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수동으로 특정 객체에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">주입 대상 객체</param>
|
||||
/// <remarks>
|
||||
/// <para>순수 C# 객체나 개별 컴포넌트에 의존성을 주입할 때 사용합니다.</para>
|
||||
/// <code>
|
||||
/// var service = new MyService();
|
||||
/// InjectorAppContext.Instance.InjectInto(service);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public void InjectInto(object target)
|
||||
{
|
||||
Injector?.Inject(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서비스를 해결합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">서비스 타입</typeparam>
|
||||
/// <returns>서비스 인스턴스</returns>
|
||||
/// <remarks>
|
||||
/// <para>Injector.Resolve<T>()의 편의 래퍼입니다.</para>
|
||||
/// <code>
|
||||
/// var logger = InjectorAppContext.Instance.Get<ILogService>();
|
||||
/// logger.Log("Hello!");
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public T Get<T>() where T : class
|
||||
{
|
||||
return Injector?.Resolve<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 해결을 시도합니다. 실패 시 null을 반환합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">서비스 타입</typeparam>
|
||||
/// <returns>서비스 인스턴스 또는 null</returns>
|
||||
/// <remarks>
|
||||
/// <para>선택적 서비스를 안전하게 가져올 때 사용합니다.</para>
|
||||
/// <code>
|
||||
/// var analytics = InjectorAppContext.Instance.TryGet<IAnalyticsService>();
|
||||
/// analytics?.TrackEvent("game_start");
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public T TryGet<T>() where T : class
|
||||
{
|
||||
return Injector?.TryResolve<T>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb9ed33d32cbc6d46a968933981839a1
|
||||
372
Assets/Scripts/UVC/core/Injector/InjectorSceneContext.cs
Normal file
372
Assets/Scripts/UVC/core/Injector/InjectorSceneContext.cs
Normal file
@@ -0,0 +1,372 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 씬 레벨 Injector 컨텍스트 - 현재 씬의 서비스 관리
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>현재 씬 수명 동안만 유지되는 서비스들을 등록하고 관리합니다.</para>
|
||||
/// <para>씬이 언로드될 때 Scene 라이프사이클 서비스들이 자동으로 정리됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 실행 순서 ]</b></para>
|
||||
/// <para>DefaultExecutionOrder(-900)으로 InjectorAppContext(-1000) 이후에 초기화됩니다.</para>
|
||||
/// <para>AppContext가 준비되지 않았으면 코루틴으로 대기합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 역할 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Scene 라이프사이클 서비스 등록</description></item>
|
||||
/// <item><description>씬 오브젝트들에 자동 의존성 주입</description></item>
|
||||
/// <item><description>씬 언로드 시 Scene 서비스 자동 정리</description></item>
|
||||
/// <item><description>동적 생성 객체에 의존성 주입 지원</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ Unity Editor 설정 ]</b></para>
|
||||
/// <list type="number">
|
||||
/// <item><description>각 씬에 빈 GameObject 생성 → 이름: "SceneContext"</description></item>
|
||||
/// <item><description>InjectorSceneContext (또는 상속받은 클래스) 컴포넌트 추가</description></item>
|
||||
/// <item><description>Inspector에서 Scene Prefab 목록 설정 (선택)</description></item>
|
||||
/// <item><description>Auto Inject 옵션 설정 (선택)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 방법 - 상속 ]</b></para>
|
||||
/// <code>
|
||||
/// public class BattleSceneContext : InjectorSceneContext
|
||||
/// {
|
||||
/// [SerializeField] private BattleUI battleUIPrefab;
|
||||
///
|
||||
/// protected override void RegisterSceneServices()
|
||||
/// {
|
||||
/// base.RegisterSceneServices();
|
||||
///
|
||||
/// // Type A: Scene 라이프사이클 순수 C#
|
||||
/// Injector.Register<ISceneConfig, SceneConfig>(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Type B: Scene 라이프사이클 MonoBehaviour
|
||||
/// Injector.Register<IEnemySpawner, EnemySpawner>(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Type C: Scene 라이프사이클 Prefab
|
||||
/// Injector.RegisterPrefab<IBattleUI>(battleUIPrefab.gameObject, ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Type D: Scene 단위 Singleton
|
||||
/// Injector.RegisterSingleton<LevelManager>();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 자동 주입 옵션 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>autoInjectSceneObjects:</b> 씬 로드 시 모든 오브젝트에 자동 주입</description></item>
|
||||
/// <item><description><b>targetObjects:</b> 특정 오브젝트에만 주입 (비어있으면 전체)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 이벤트 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>OnSceneServicesRegistered: Scene 서비스 등록 완료 후 발생</description></item>
|
||||
/// <item><description>OnSceneInjectionCompleted: 의존성 주입 완료 후 발생</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ App vs Scene 라이프사이클 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>구분</term>
|
||||
/// <description>특징</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>App</term>
|
||||
/// <description>게임 전체 공유: 설정, 네트워크, 오디오 등</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Scene</term>
|
||||
/// <description>현재 씬만: 레벨 매니저, 적 스포너, 씬 UI 등</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="InjectorAppContext"/>
|
||||
/// <seealso cref="ServiceLifetime"/>
|
||||
[DefaultExecutionOrder(-900)]
|
||||
public class InjectorSceneContext : MonoBehaviour
|
||||
{
|
||||
#region Inspector Fields
|
||||
|
||||
[Header("Scene Lifetime Prefabs")]
|
||||
[Tooltip("현재 씬 수명 동안만 유지될 Prefab들")]
|
||||
[SerializeField]
|
||||
private List<MonoBehaviour> scenePrefabs = new();
|
||||
|
||||
[Header("Auto Injection")]
|
||||
[Tooltip("씬 로드 시 자동으로 모든 GameObject에 의존성 주입")]
|
||||
[SerializeField]
|
||||
private bool autoInjectSceneObjects = true;
|
||||
|
||||
[Tooltip("특정 GameObject들에만 의존성 주입 (비어있으면 전체 씬)")]
|
||||
[SerializeField]
|
||||
private List<GameObject> targetObjects = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// App Context의 Injector 참조
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>InjectorAppContext.Instance.Injector에 대한 편의 프로퍼티입니다.</para>
|
||||
/// <para>AppContext가 null이면 null을 반환합니다.</para>
|
||||
/// </remarks>
|
||||
protected IInjector Injector => InjectorAppContext.Instance?.Injector;
|
||||
|
||||
/// <summary>
|
||||
/// 초기화 완료 여부
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Initialize() 완료 후 true가 됩니다.</para>
|
||||
/// <para>Scene 서비스 등록 및 의존성 주입이 완료된 상태를 나타냅니다.</para>
|
||||
/// </remarks>
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Scene 서비스 등록 완료 시 발생
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>RegisterSceneServices() 메서드 완료 후 발생합니다.</para>
|
||||
/// </remarks>
|
||||
public event System.Action OnSceneServicesRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Scene 의존성 주입 완료 시 발생
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>PerformSceneInjection() 메서드 완료 후 발생합니다.</para>
|
||||
/// <para>모든 씬 오브젝트에 의존성이 주입된 상태를 나타냅니다.</para>
|
||||
/// </remarks>
|
||||
public event System.Action OnSceneInjectionCompleted;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake - InjectorAppContext 준비 확인 후 초기화
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
// InjectorAppContext가 준비될 때까지 대기
|
||||
if (InjectorAppContext.Instance == null || !InjectorAppContext.Instance.IsInitialized)
|
||||
{
|
||||
Debug.LogWarning("[InjectorSceneContext] InjectorAppContext is not initialized. Waiting...");
|
||||
StartCoroutine(WaitForAppContext());
|
||||
return;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InjectorAppContext가 준비될 때까지 대기하는 코루틴
|
||||
/// </summary>
|
||||
private System.Collections.IEnumerator WaitForAppContext()
|
||||
{
|
||||
while (InjectorAppContext.Instance == null || !InjectorAppContext.Instance.IsInitialized)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실제 초기화 수행 - 서비스 등록 및 의존성 주입
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
if (Injector == null)
|
||||
{
|
||||
Debug.LogError("[InjectorSceneContext] Injector is null. Make sure InjectorAppContext exists in the scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Scene 서비스 등록
|
||||
RegisterSceneServices();
|
||||
|
||||
// 자동 의존성 주입
|
||||
if (autoInjectSceneObjects)
|
||||
{
|
||||
PerformSceneInjection();
|
||||
}
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnDestroy - Scene 서비스 정리
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>씬 언로드 시 자동으로 호출됩니다.</para>
|
||||
/// <para>Injector.OnSceneUnloaded()를 호출하여 Scene 라이프사이클 서비스들을 정리합니다.</para>
|
||||
/// </remarks>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
// Scene 라이프사이클 서비스 정리
|
||||
Injector?.OnSceneUnloaded();
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Registration
|
||||
|
||||
/// <summary>
|
||||
/// Scene 서비스 등록을 수행합니다. 자식 클래스에서 오버라이드하여 추가 서비스를 등록하세요.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 호출 시점 ]</b></para>
|
||||
/// <para>Initialize() 메서드에서 Injector 확인 후 호출됩니다.</para>
|
||||
///
|
||||
/// <para><b>[ 오버라이드 시 주의사항 ]</b></para>
|
||||
/// <para>base.RegisterSceneServices()를 먼저 호출하여 Inspector에서 할당한 Prefab들이 등록되도록 하세요.</para>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// protected override void RegisterSceneServices()
|
||||
/// {
|
||||
/// base.RegisterSceneServices(); // Inspector Prefab 등록
|
||||
///
|
||||
/// // Type A: Scene 순수 C# 클래스
|
||||
/// Injector.Register<ISceneConfig, SceneConfig>(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Type B: Scene MonoBehaviour
|
||||
/// Injector.Register<IEnemySpawner, EnemySpawner>(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Type D: Scene Singleton
|
||||
/// Injector.RegisterSingleton<LevelManager>();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
protected virtual void RegisterSceneServices()
|
||||
{
|
||||
// Inspector에서 할당된 Scene Prefab 등록
|
||||
foreach (var prefab in scenePrefabs)
|
||||
{
|
||||
if (prefab != null)
|
||||
{
|
||||
RegisterPrefabByType(prefab, ServiceLifetime.Scene);
|
||||
}
|
||||
}
|
||||
|
||||
// 자기 자신 등록
|
||||
Injector.RegisterInstance<InjectorSceneContext>(this, ServiceLifetime.Scene);
|
||||
|
||||
OnSceneServicesRegistered?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefab을 해당 타입으로 등록합니다. (리플렉션 사용)
|
||||
/// </summary>
|
||||
private void RegisterPrefabByType(MonoBehaviour prefab, ServiceLifetime lifetime)
|
||||
{
|
||||
var type = prefab.GetType();
|
||||
|
||||
// 리플렉션을 사용하여 RegisterPrefab<T> 호출
|
||||
var method = typeof(IInjector).GetMethod("RegisterPrefab", new[] { typeof(GameObject), typeof(ServiceLifetime) });
|
||||
var genericMethod = method.MakeGenericMethod(type);
|
||||
genericMethod.Invoke(Injector, new object[] { prefab.gameObject, lifetime });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Injection
|
||||
|
||||
/// <summary>
|
||||
/// 씬 오브젝트들에 의존성 주입을 수행합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 동작 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>targetObjects가 설정되어 있으면 해당 오브젝트들에만 주입</description></item>
|
||||
/// <item><description>비어있으면 씬의 모든 루트 오브젝트와 자식들에 주입</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
private void PerformSceneInjection()
|
||||
{
|
||||
if (targetObjects.Count > 0)
|
||||
{
|
||||
// 특정 오브젝트들에만 주입
|
||||
foreach (var target in targetObjects)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
Injector.InjectGameObject(target, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 전체 씬에 주입
|
||||
var scene = gameObject.scene;
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
foreach (var rootObj in rootObjects)
|
||||
{
|
||||
Injector.InjectGameObject(rootObj, true);
|
||||
}
|
||||
}
|
||||
|
||||
OnSceneInjectionCompleted?.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// 수동으로 특정 GameObject에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">주입 대상 GameObject</param>
|
||||
/// <param name="includeChildren">자식 오브젝트 포함 여부 (기본값: true)</param>
|
||||
/// <remarks>
|
||||
/// <para>autoInjectSceneObjects가 false일 때 수동으로 주입할 때 사용합니다.</para>
|
||||
/// </remarks>
|
||||
public void InjectInto(GameObject target, bool includeChildren = true)
|
||||
{
|
||||
Injector?.InjectGameObject(target, includeChildren);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수동으로 특정 객체에 의존성을 주입합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">주입 대상 객체</param>
|
||||
public void InjectInto(object target)
|
||||
{
|
||||
Injector?.Inject(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동적으로 생성된 GameObject에 의존성을 주입합니다. Instantiate 후 호출하세요.
|
||||
/// </summary>
|
||||
/// <param name="instantiatedObject">Instantiate로 생성된 오브젝트</param>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // 적 스폰 시
|
||||
/// var enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);
|
||||
/// InjectorSceneContext.Instance.InjectInstantiated(enemy);
|
||||
///
|
||||
/// // 또는 FindObjectOfType 사용
|
||||
/// var sceneContext = FindObjectOfType<InjectorSceneContext>();
|
||||
/// sceneContext.InjectInstantiated(enemy);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public void InjectInstantiated(GameObject instantiatedObject)
|
||||
{
|
||||
Injector?.InjectGameObject(instantiatedObject, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4863b44e18fd5d14ca59287fa6361032
|
||||
458
Assets/Scripts/UVC/core/Injector/ServiceDescriptor.cs
Normal file
458
Assets/Scripts/UVC/core/Injector/ServiceDescriptor.cs
Normal file
@@ -0,0 +1,458 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 서비스 등록 타입을 정의하는 열거형
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>Injector에 서비스를 등록할 때 사용되는 4가지 타입 + Instance 타입을 정의합니다.</para>
|
||||
/// <para>각 타입에 따라 인스턴스 생성 방식과 Unity 통합 수준이 달라집니다.</para>
|
||||
///
|
||||
/// <para><b>[ 타입별 특징 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>타입</term>
|
||||
/// <description>설명</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>Type A (PureCSharp)</term>
|
||||
/// <description>new T()로 생성, GameObject 불필요</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type B (MonoBehaviour)</term>
|
||||
/// <description>런타임에 새 GameObject 생성 후 AddComponent</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type C (Prefab)</term>
|
||||
/// <description>등록된 Prefab을 Instantiate하여 생성</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Type D (Singleton)</term>
|
||||
/// <description>기존 Singleton<T> 클래스의 Instance 연동</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Instance</term>
|
||||
/// <description>이미 생성된 인스턴스를 직접 등록</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // Type A: 순수 C# 클래스
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type B: MonoBehaviour 동적 생성
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Type C: Prefab 기반
|
||||
/// Injector.RegisterPrefab<IUIManager>(uiManagerPrefab, ServiceLifetime.App);
|
||||
///
|
||||
/// // Type D: Singleton 연동
|
||||
/// Injector.RegisterSingleton<SettingsManager>();
|
||||
///
|
||||
/// // Instance: 직접 등록
|
||||
/// Injector.RegisterInstance<IConfig>(existingConfig);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ServiceDescriptor"/>
|
||||
/// <seealso cref="Injector"/>
|
||||
public enum ServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// 순수 C# 클래스 (Type A)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>MonoBehaviour를 상속하지 않는 일반 C# 클래스</description></item>
|
||||
/// <item><description>Activator.CreateInstance()로 생성</description></item>
|
||||
/// <item><description>GameObject 없이 동작</description></item>
|
||||
/// <item><description>가장 가벼운 형태의 서비스</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 ]</b></para>
|
||||
/// <code>Injector.Register<IService, ServiceImpl>(lifetime);</code>
|
||||
/// </remarks>
|
||||
PureCSharp,
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 상속 클래스 - 동적 생성 (Type B)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>런타임에 새 GameObject가 생성됨</description></item>
|
||||
/// <item><description>new GameObject().AddComponent<T>()로 생성</description></item>
|
||||
/// <item><description>GameObject 이름: "[Injector] {타입명}"</description></item>
|
||||
/// <item><description>App 라이프사이클이면 DontDestroyOnLoad 적용</description></item>
|
||||
/// <item><description>Unity 생명주기 메서드 (Awake, Start, Update 등) 사용 가능</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 ]</b></para>
|
||||
/// <code>Injector.Register<IAudioManager, AudioManager>(lifetime);</code>
|
||||
///
|
||||
/// <para><b>[ Type C (Prefab)와의 차이 ]</b></para>
|
||||
/// <para>Type B는 코드로만 구성되어 Inspector 설정이 불가능합니다.</para>
|
||||
/// <para>Inspector에서 AudioSource, 참조 오브젝트 등을 설정해야 한다면 Type C를 사용하세요.</para>
|
||||
/// </remarks>
|
||||
MonoBehaviour,
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour + Prefab 기반 (Type C)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>미리 만들어진 Prefab을 Instantiate하여 생성</description></item>
|
||||
/// <item><description>Inspector에서 설정한 값들이 유지됨</description></item>
|
||||
/// <item><description>자식 오브젝트, 참조 컴포넌트 등 복잡한 구조 지원</description></item>
|
||||
/// <item><description>App 라이프사이클이면 DontDestroyOnLoad 적용</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 ]</b></para>
|
||||
/// <code>
|
||||
/// // MonoBehaviour 타입으로 직접 등록
|
||||
/// Injector.RegisterPrefab<UIManager>(uiManagerPrefab, lifetime);
|
||||
///
|
||||
/// // 인터페이스로 등록 (GameObject 사용)
|
||||
/// Injector.RegisterPrefab<IUIManager>(uiManagerPrefab.gameObject, lifetime);
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ 적합한 경우 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Canvas, Button 등 UI 컴포넌트가 필요한 UIManager</description></item>
|
||||
/// <item><description>AudioSource가 미리 설정된 AudioManager</description></item>
|
||||
/// <item><description>자식 오브젝트가 있는 복잡한 구조의 서비스</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
Prefab,
|
||||
|
||||
/// <summary>
|
||||
/// 기존 Singleton 연동 (Type D)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Singleton<T>, SingletonApp<T>, SingletonScene<T> 상속 클래스 지원</description></item>
|
||||
/// <item><description>기존 Instance 프로퍼티를 통해 인스턴스 획득</description></item>
|
||||
/// <item><description>Injector가 인스턴스를 직접 생성하지 않음</description></item>
|
||||
/// <item><description>라이프사이클은 Singleton 타입에 따라 자동 결정</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ Singleton 타입별 라이프사이클 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Singleton<T>: App (순수 C#)</description></item>
|
||||
/// <item><description>SingletonApp<T>: App (MonoBehaviour + DontDestroyOnLoad)</description></item>
|
||||
/// <item><description>SingletonScene<T>: Scene (MonoBehaviour, 씬 전환 시 파괴)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 ]</b></para>
|
||||
/// <code>Injector.RegisterSingleton<SettingsManager>();</code>
|
||||
///
|
||||
/// <para><b>[ 사용 방법 ]</b></para>
|
||||
/// <code>
|
||||
/// // [Inject] 어트리뷰트 사용
|
||||
/// [Inject] private SettingsManager _settings;
|
||||
///
|
||||
/// // 기존 방식도 동일하게 동작
|
||||
/// SettingsManager.Instance.Save();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
Singleton,
|
||||
|
||||
/// <summary>
|
||||
/// 이미 생성된 인스턴스 등록
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>외부에서 생성한 인스턴스를 직접 등록</description></item>
|
||||
/// <item><description>Injector가 인스턴스 생성을 담당하지 않음</description></item>
|
||||
/// <item><description>등록된 인스턴스에 대해 의존성 주입이 수행되지 않음</description></item>
|
||||
/// <item><description>Context 자신을 등록할 때 주로 사용</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 등록 방법 ]</b></para>
|
||||
/// <code>
|
||||
/// // 이미 생성된 인스턴스 등록
|
||||
/// var config = new AppConfig { Debug = true };
|
||||
/// Injector.RegisterInstance<IAppConfig>(config, lifetime);
|
||||
///
|
||||
/// // MonoBehaviour 인스턴스 등록
|
||||
/// Injector.RegisterInstance<InjectorAppContext>(this, ServiceLifetime.App);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
Instance
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 등록 정보를 담는 메타데이터 클래스
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>Injector에 등록된 각 서비스에 대한 메타데이터를 저장합니다.</para>
|
||||
/// <para>서비스의 타입 정보, 라이프사이클, 생성 방식, 캐시된 인스턴스 등을 관리합니다.</para>
|
||||
///
|
||||
/// <para><b>[ 역할 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>서비스 타입 정보 저장 (인터페이스 타입, 구현 타입)</description></item>
|
||||
/// <item><description>라이프사이클 및 서비스 타입 관리</description></item>
|
||||
/// <item><description>인스턴스 캐싱 (App/Scene 라이프사이클)</description></item>
|
||||
/// <item><description>Prefab 소스 및 Factory 함수 저장</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 생성자 종류 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>생성자</term>
|
||||
/// <description>용도</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>(Type, Type, Lifetime)</term>
|
||||
/// <description>순수 C# 또는 MonoBehaviour 타입 등록</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(Type, object, Lifetime)</term>
|
||||
/// <description>이미 생성된 인스턴스 등록</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(Type, GameObject, Lifetime)</term>
|
||||
/// <description>Prefab 기반 등록</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(Type, Func, Lifetime)</term>
|
||||
/// <description>Factory 함수 기반 등록</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(Type)</term>
|
||||
/// <description>Singleton 타입 등록</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 내부 사용 ]</b></para>
|
||||
/// <para>이 클래스는 internal로 선언되어 UVC.Core 어셈블리 내부에서만 사용됩니다.</para>
|
||||
/// <para>외부에서는 Injector의 등록 메서드를 통해 간접적으로 ServiceDescriptor가 생성됩니다.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ServiceType"/>
|
||||
/// <seealso cref="ServiceLifetime"/>
|
||||
/// <seealso cref="Injector"/>
|
||||
internal class ServiceDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// 서비스 인터페이스 또는 기본 타입
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Injector.Resolve<T>()에서 T에 해당하는 타입입니다.</para>
|
||||
/// <para>예: ILogService, IAudioManager 등의 인터페이스 또는 구체 타입</para>
|
||||
/// </remarks>
|
||||
public Type InterfaceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 실제 구현 타입
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>실제로 인스턴스화되는 클래스 타입입니다.</para>
|
||||
/// <para>예: ConsoleLogger, AudioManager 등</para>
|
||||
/// <para>Singleton 타입의 경우 InterfaceType과 동일합니다.</para>
|
||||
/// </remarks>
|
||||
public Type ImplementationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 라이프사이클
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>인스턴스의 수명 주기를 결정합니다.</para>
|
||||
/// <para>App: 앱 전체, Scene: 현재 씬, Transient: 매번 새로 생성</para>
|
||||
/// </remarks>
|
||||
public ServiceLifetime Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 서비스 등록 타입
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>인스턴스 생성 방식을 결정합니다.</para>
|
||||
/// <para>PureCSharp, MonoBehaviour, Prefab, Singleton, Instance 중 하나</para>
|
||||
/// </remarks>
|
||||
public ServiceType ServiceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 인스턴스 (Transient가 아닌 경우)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>App/Scene 라이프사이클인 경우 첫 Resolve 시 생성된 인스턴스가 캐싱됩니다.</para>
|
||||
/// <para>MonoBehaviour의 경우 파괴 여부를 확인하여 null이면 다시 생성합니다.</para>
|
||||
/// <para>Transient 라이프사이클에서는 사용되지 않습니다.</para>
|
||||
/// </remarks>
|
||||
public object Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefab 소스 (ServiceType.Prefab인 경우)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>RegisterPrefab으로 등록된 경우 원본 Prefab GameObject가 저장됩니다.</para>
|
||||
/// <para>Resolve 시 이 Prefab을 Instantiate하여 인스턴스를 생성합니다.</para>
|
||||
/// </remarks>
|
||||
public GameObject PrefabSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 팩토리 함수 (커스텀 생성 로직이 필요한 경우)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>RegisterFactory로 등록된 경우 인스턴스 생성에 사용되는 함수입니다.</para>
|
||||
/// <para>IInjector를 파라미터로 받아 다른 서비스에 대한 의존성 해결이 가능합니다.</para>
|
||||
/// <code>
|
||||
/// Injector.RegisterFactory<ISceneConfig>(injector => new SceneConfig
|
||||
/// {
|
||||
/// SceneName = SceneManager.GetActiveScene().name,
|
||||
/// Logger = injector.Resolve<ILogService>()
|
||||
/// });
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public Func<IInjector, object> Factory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefab 기반인지 확인
|
||||
/// </summary>
|
||||
public bool IsPrefab => ServiceType == ServiceType.Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 Singleton인지 확인
|
||||
/// </summary>
|
||||
public bool IsSingleton => ServiceType == ServiceType.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 기반인지 확인 (동적 생성 또는 Prefab)
|
||||
/// </summary>
|
||||
public bool IsMonoBehaviour => ServiceType == ServiceType.MonoBehaviour ||
|
||||
ServiceType == ServiceType.Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// 순수 C# 클래스용 생성자
|
||||
/// </summary>
|
||||
public ServiceDescriptor(Type interfaceType, Type implementationType, ServiceLifetime lifetime)
|
||||
{
|
||||
InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType));
|
||||
ImplementationType = implementationType ?? throw new ArgumentNullException(nameof(implementationType));
|
||||
Lifetime = lifetime;
|
||||
ServiceType = DetermineServiceType(implementationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 인스턴스 직접 등록용 생성자
|
||||
/// </summary>
|
||||
public ServiceDescriptor(Type interfaceType, object instance, ServiceLifetime lifetime)
|
||||
{
|
||||
InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType));
|
||||
ImplementationType = instance?.GetType() ?? throw new ArgumentNullException(nameof(instance));
|
||||
Instance = instance;
|
||||
Lifetime = lifetime;
|
||||
ServiceType = ServiceType.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefab 등록용 생성자
|
||||
/// </summary>
|
||||
public ServiceDescriptor(Type interfaceType, GameObject prefab, ServiceLifetime lifetime)
|
||||
{
|
||||
InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType));
|
||||
PrefabSource = prefab ?? throw new ArgumentNullException(nameof(prefab));
|
||||
|
||||
var component = prefab.GetComponent(interfaceType);
|
||||
if (component == null)
|
||||
{
|
||||
throw new ArgumentException($"Prefab does not contain component of type {interfaceType.Name}");
|
||||
}
|
||||
|
||||
ImplementationType = component.GetType();
|
||||
Lifetime = lifetime;
|
||||
ServiceType = ServiceType.Prefab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 팩토리 함수 등록용 생성자
|
||||
/// </summary>
|
||||
public ServiceDescriptor(Type interfaceType, Func<IInjector, object> factory, ServiceLifetime lifetime, ServiceType serviceType = ServiceType.PureCSharp)
|
||||
{
|
||||
InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType));
|
||||
Factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
ImplementationType = interfaceType;
|
||||
Lifetime = lifetime;
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singleton 타입 등록용 생성자
|
||||
/// </summary>
|
||||
public ServiceDescriptor(Type singletonType)
|
||||
{
|
||||
InterfaceType = singletonType ?? throw new ArgumentNullException(nameof(singletonType));
|
||||
ImplementationType = singletonType;
|
||||
ServiceType = ServiceType.Singleton;
|
||||
|
||||
// Singleton 타입에 따라 라이프사이클 자동 결정
|
||||
if (IsSingletonAppType(singletonType))
|
||||
{
|
||||
Lifetime = ServiceLifetime.App;
|
||||
}
|
||||
else if (IsSingletonSceneType(singletonType))
|
||||
{
|
||||
Lifetime = ServiceLifetime.Scene;
|
||||
}
|
||||
else
|
||||
{
|
||||
Lifetime = ServiceLifetime.App; // 기본값
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 구현 타입에 따라 ServiceType 결정
|
||||
/// </summary>
|
||||
private ServiceType DetermineServiceType(Type type)
|
||||
{
|
||||
if (typeof(UnityEngine.MonoBehaviour).IsAssignableFrom(type))
|
||||
{
|
||||
return ServiceType.MonoBehaviour;
|
||||
}
|
||||
return ServiceType.PureCSharp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SingletonApp 타입인지 확인
|
||||
/// </summary>
|
||||
private bool IsSingletonAppType(Type type)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
if (baseType.IsGenericType &&
|
||||
baseType.GetGenericTypeDefinition().Name.StartsWith("SingletonApp"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SingletonScene 타입인지 확인
|
||||
/// </summary>
|
||||
private bool IsSingletonSceneType(Type type)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
while (baseType != null)
|
||||
{
|
||||
if (baseType.IsGenericType &&
|
||||
baseType.GetGenericTypeDefinition().Name.StartsWith("SingletonScene"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40693cd93d05170449183fffd29d9bf1
|
||||
135
Assets/Scripts/UVC/core/Injector/ServiceLifetime.cs
Normal file
135
Assets/Scripts/UVC/core/Injector/ServiceLifetime.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
namespace UVC.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 서비스 인스턴스의 라이프사이클을 정의하는 열거형
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 개요 ]</b></para>
|
||||
/// <para>DI 컨테이너에 등록되는 서비스의 수명 주기를 결정합니다.</para>
|
||||
/// <para>라이프사이클에 따라 인스턴스의 생성 시점, 캐싱 여부, 파괴 시점이 달라집니다.</para>
|
||||
///
|
||||
/// <para><b>[ 라이프사이클 비교 ]</b></para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>라이프사이클</term>
|
||||
/// <description>특징</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>App</term>
|
||||
/// <description>앱 전체에서 단일 인스턴스, DontDestroyOnLoad 적용</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Scene</term>
|
||||
/// <description>현재 씬에서만 유효, 씬 전환 시 자동 파괴</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Transient</term>
|
||||
/// <description>Resolve 호출 시마다 새 인스턴스 생성</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 사용 예시 ]</b></para>
|
||||
/// <code>
|
||||
/// // App 라이프사이클 - 게임 전체에서 공유
|
||||
/// Injector.Register<ILogService, ConsoleLogger>(ServiceLifetime.App);
|
||||
/// Injector.Register<IAudioManager, AudioManager>(ServiceLifetime.App);
|
||||
///
|
||||
/// // Scene 라이프사이클 - 현재 씬에서만 유효
|
||||
/// Injector.Register<IEnemySpawner, EnemySpawner>(ServiceLifetime.Scene);
|
||||
/// Injector.Register<ILevelManager, LevelManager>(ServiceLifetime.Scene);
|
||||
///
|
||||
/// // Transient 라이프사이클 - 매번 새 인스턴스
|
||||
/// Injector.Register<IRequestHandler, RequestHandler>(ServiceLifetime.Transient);
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>[ MonoBehaviour와의 관계 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>App + MonoBehaviour: DontDestroyOnLoad가 자동 적용됨</description></item>
|
||||
/// <item><description>Scene + MonoBehaviour: 씬 전환 시 GameObject가 함께 파괴됨</description></item>
|
||||
/// <item><description>Transient + MonoBehaviour: 매번 새 GameObject가 생성됨 (권장하지 않음)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 주의사항 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Scene 라이프사이클 서비스가 App 라이프사이클 서비스를 의존하는 것은 안전함</description></item>
|
||||
/// <item><description>App 라이프사이클 서비스가 Scene 라이프사이클 서비스를 의존하면 씬 전환 후 null 참조 발생 가능</description></item>
|
||||
/// <item><description>Transient는 상태를 공유하지 않아야 하는 경우에만 사용 (예: HTTP 요청 핸들러)</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Injector"/>
|
||||
/// <seealso cref="ServiceDescriptor"/>
|
||||
/// <seealso cref="InjectorAppContext"/>
|
||||
/// <seealso cref="InjectorSceneContext"/>
|
||||
public enum ServiceLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// 앱 전체 수명 동안 유지 (씬 전환 시에도 유지)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>애플리케이션 시작부터 종료까지 단일 인스턴스 유지</description></item>
|
||||
/// <item><description>첫 Resolve 시 인스턴스 생성 후 캐싱</description></item>
|
||||
/// <item><description>MonoBehaviour인 경우 DontDestroyOnLoad 자동 적용</description></item>
|
||||
/// <item><description>InjectorAppContext.RegisterServices()에서 등록 권장</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 적합한 서비스 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>로깅 서비스 (ILogService)</description></item>
|
||||
/// <item><description>오디오 매니저 (IAudioManager)</description></item>
|
||||
/// <item><description>네트워크 매니저 (INetworkManager)</description></item>
|
||||
/// <item><description>게임 설정 (ISettingsManager)</description></item>
|
||||
/// <item><description>전역 UI 매니저 (IUIManager)</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
App,
|
||||
|
||||
/// <summary>
|
||||
/// 현재 씬 수명 동안만 유지 (씬 전환 시 소멸)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>씬 로드 시 인스턴스 생성, 씬 언로드 시 자동 파괴</description></item>
|
||||
/// <item><description>InjectorSceneContext.OnDestroy()에서 자동 정리</description></item>
|
||||
/// <item><description>MonoBehaviour인 경우 GameObject도 함께 파괴</description></item>
|
||||
/// <item><description>IDisposable 구현 시 Dispose() 자동 호출</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 적합한 서비스 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>적 스포너 (IEnemySpawner)</description></item>
|
||||
/// <item><description>레벨 매니저 (ILevelManager)</description></item>
|
||||
/// <item><description>씬별 UI (ISceneUI)</description></item>
|
||||
/// <item><description>씬별 설정 (ISceneConfig)</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
Scene,
|
||||
|
||||
/// <summary>
|
||||
/// 매번 새로운 인스턴스 생성 (싱글톤이 아님)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>[ 특징 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Resolve() 호출 시마다 새 인스턴스 생성</description></item>
|
||||
/// <item><description>인스턴스가 캐싱되지 않음</description></item>
|
||||
/// <item><description>각 인스턴스는 독립적인 상태 유지</description></item>
|
||||
/// <item><description>[Inject] 필드가 있으면 매번 의존성 주입 수행</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 적합한 서비스 ]</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>HTTP 요청 핸들러 (IRequestHandler)</description></item>
|
||||
/// <item><description>이벤트 처리기 (IEventHandler)</description></item>
|
||||
/// <item><description>일회성 작업 객체</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>[ 주의사항 ]</b></para>
|
||||
/// <para>MonoBehaviour와 함께 사용 시 매번 새 GameObject가 생성되므로</para>
|
||||
/// <para>성능에 영향을 줄 수 있습니다. 순수 C# 클래스에만 사용을 권장합니다.</para>
|
||||
/// </remarks>
|
||||
Transient
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/core/Injector/ServiceLifetime.cs.meta
Normal file
2
Assets/Scripts/UVC/core/Injector/ServiceLifetime.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d83da777d7d649343bcc184d8f56d4a0
|
||||
Reference in New Issue
Block a user