본문으로 건너뛰기

UNInject의 방식

앞서 살펴본 D.I 개념Provider / Consumer를 Unity 씬·직렬화·생명주기에 맞춘 구현이 UNInject입니다.
런타임에 계층을 뒤지며 연결하지 않습니다.
에디터와 컴파일 단계에서 연결을 고정하고, 실행 시에는 Dictionary 조회와 생성된 세터를 통한 직접 대입으로 끝냅니다.


핵심 기조

코드 레벨과 에디터의 적절한 경계선. 그리고 사용성

최우선 과제인 최적화. 그 이후는 사용성으로만 채웠습니다.
기존 D.I 솔루션들의 문제점인, Pure C# Service 레이어의 특수성과 Unity의 레이어를 분단하지 않음과 동시에,
사용자가 이 생명 주기를 관리하는 데에 있어 어려움이 없도록 설계되었습니다.

  1. 비용은 무조건 런타임 이전으로 보낸다
    로컬 참조는 베이크·직렬화, 전역·씬 참조는 생성 플랜(또는 폴백) + 레지스트리 조회로 처리합니다.
    핫패스에서 계층 전체를 훑거나 매번 리플렉션으로 필드를 찾는 구조가 기본값이 아닙니다.

  2. 키는 하나의 모델로 통일한다
    등록과 조회는 RegistryKey(타입, Id) 로만 이루어집니다.
    무키는 빈 Id로 표현하고, 네임드는 동일한 Id를 Provider·Consumer 양쪽에 맞춥니다. 특별 케이스 레지스트리는 두지 않습니다.

  3. 스코프는 층으로 고정하고, 틱은 전파하지 않는다
    전역·씬·로컬 세 층은 Installer 수명과 맞물립니다. 관리 직관성과 귀결되며, 코드 베이스를 뒤질 필요가 없습니다.
    Create<T>() 로 붙는 틱·파괴 콜백은 호출한 그 Installer에만 귀속되며, 부모 스코프로 자동 전파되지 않습니다.

  4. 순회·파괴 순서는 명시적으로 고정한다
    틱 목록은 스냅샷으로 돌고, 목록 변경은 다음 프레임에 반영됩니다.
    스코프 파괴 시 IScopeDestroyable → 틱 정리 순서가 코드에 박혀 있어, “언제 정리되는지”가 숨지 않습니다.

  5. Mono 소비자와 순수 C# 서비스는 같은 Resolve 규칙을 쓴다
    ObjectInstaller의 필드 주입과 IScope.Create<T>()의 생성자·필드 주입은 같은 키·같은 체인 을 공유합니다.
    엔진 컴포넌트와 순수 클래스를 다른 두 세계로 쪼개지 않습니다.

  6. 누락과 폴백은 숨기지 않는다
    빈 글로벌 레지스트리, partial 누락으로 인한 폴백,
    베이크와 필드 선언 불일치 등은 에디터·빌드 전처리 경로에서 진단할 수 있게 열어 둡니다.

    조용히 느려지는 상태를 정상으로 취급하지 않습니다. 즉시 진단 후 경고합니다.

  7. 콜백은 계약으로 드러낸다
    주입 완료(IInjected), 풀 재주입(IPoolInjectionTarget), 스코프 파괴(IScopeDestroyable) 등은
    인터페이스와 호출 순서가 코드에 고정되어 있습니다. 암묵적인 Unity 메시지에만 기대지 않습니다.


구성 원리 (네 가지)

  1. 에디터 타임 베이크
    로컬 트리 안의 [Inject] 필드는 개발 중 계층 스캔으로 참조가 인스펙터에 기록되고,
    런타임에는 Unity 직렬화 복원만 수행합니다. 이 구간에서 GetComponent·Find 등 런타임 탐색은 발생하지 않습니다.

  2. Roslyn 소스 제너레이터
    [GlobalInject] / [SceneInject]는 컴파일 시 전용 세터가 생성됩니다.
    partial 클래스와 결합된 타입은 생성 플랜 경로에서 리플렉션을 사용하지 않으며, 생성 코드는 캐스트, 대입으로만 구성됩니다.
    Expression.Compile은 쓰지 않으므로 IL2CPP·AOT 환경에서도 해당 경로는 안전합니다.

  3. 3-Tier 스코프
    Provider는 전역(Master) / 씬(Scene) / 로컬(Object) 세 층으로 고정됩니다.
    Consumer는 속성만으로 어느 층의 무엇을 요구하는지가 코드에 드러납니다.
    수명은 씬과 DDOL 규칙에 맞게 구조적으로 분리됩니다.

  4. Manager(Mono)와 순수 C# 서비스
    씬의 ManagerMonoBehaviour + Referral로 레지스트리에 올리고,
    순수 클래스Create<T>() 등으로 만들며 동일한 Resolve 규칙으로 Manager를 받습니다.
    엔진 바운드 객체와 도메인 로직이 한 주입 모델을 공유합니다.

위 네 가지는 동시에 적용되는 설계이며, “Unity에서 쓰기 쉬운 D.I”와 “Provider/Consumer 분리”를 한 파이프라인으로 묶습니다.


성능·검증

  • 주입(필드 적용) 비용: 런타임은 RegistryKey 해시 조회 + (Roslyn 경로) 인라인 세터입니다.
    VContainer와 동일하게 O(1) 상수 복잡도의 조회·주입 모델을 전제로 합니다.

  • 벤치마크: 스냅샷·베이크·코드젠 조합 아래에서 Zenject 대비 최대 약 20배 수준의 주입 처리량 차이가 측정된 결과가 있습니다.
    (동일 조건 Best PERFECT, Worst CRITICAL 비교.) (Critiqued)

  • GC: Resolve 경로에서의 불필요한 탐색·박싱·반복 할당을 구조적으로 배제합니다.
    베이크와 코드젠이 그 비용을 에디터·컴파일 타임으로 이전합니다.

수치·그래프는 UNInject 소개(메인) 페이지성능 그래프 섹션에서 확인합니다.