VContainer 는 스코프의 Awake 안에서 컨테이너를 세운다
builder.Register<IGreeter, ConsoleGreeter>(Lifetime.Singleton);
builder.RegisterEntryPoint<GameEntryPoint>(); // Start() 는 언제 불릴까
등록 두 줄을 적고 실행하면 로그가 찍힙니다. 그 사이에 무엇이 지나가는지 모르면 IStartable.Start() 를 유니티의 Start 와 같은 것으로 읽고, 주입이 끝나기 전의 값을 씁니다.
VContainer 는 씬에 놓인 스코프(모노비헤이비어)의 Awake 안에서 등록을 모으고 컨테이너를 빌드하고, 진입점은 다음 프레임에 플레이어 루프에서 부릅니다. 기준 판본은 1.19.0 입니다.
등장인물은 스코프, 빌더, Resolver 셋이다
| 이름 | 정체 | 역할 |
|---|---|---|
LifetimeScope |
모노비헤이비어 | 씬에 놓는 진입점. 무엇을 등록할지 선언하고 컨테이너를 소유합니다 |
IContainerBuilder |
순수 C# | Configure() 에 넘어오는 등록 수집기 |
IObjectResolver |
순수 C# | 빌드 결과물. 실제로 객체를 만들어 주는 Resolver |
스코프가 모노비헤이비어라서 씬과 함께 죽으면서 자기가 들고 있던 컨테이너를 해제합니다.
최소 예제는 등록 두 줄이다
진입점은 모노비헤이비어가 아니라 순수 C# 클래스입니다.
public class GameEntryPoint : IStartable
{
readonly IGreeter greeter;
public GameEntryPoint(IGreeter greeter) => this.greeter = greeter; // 생성자 주입
public void Start() => greeter.Greet("VContainer");
}
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.Register<IGreeter, ConsoleGreeter>(Lifetime.Singleton);
builder.RegisterEntryPoint<GameEntryPoint>();
}
}
빈 게임오브젝트에 GameLifetimeScope 를 붙이고 실행하면 콘솔에 로그가 찍힙니다.
진입점과 모노비헤이비어는 이 순서로 불린다
스코프의 Awake 는 실행 순서가 -5000 으로 박혀 있어 다른 스크립트보다 먼저 돕니다. 그 안에서 Configure 로 등록을 모으고, 빌드하면서 순환 참조를 잡고, 진입점 디스패처를 플레이어 루프에 꽂습니다. 그 뒤로는 진입점과 모노비헤이비어 콜백이 번갈아 불립니다.
공식 문서(Entry point)는 진입점마다 “MonoBehaviour.Start() 와 거의 같은 때” 처럼 적습니다. 앞인지 뒤인지는 소스(PlayerLoopHelper)가 정합니다. 디스패처는 유니티가 모노비헤이비어 콜백을 부르는 단계 바로 앞에 진입점을, 그 그룹 맨 끝에 Post 를 꽂습니다.
모노비헤이비어의 Start 에서 준비한 값을 읽어야 하면 IPostStartable 을, 모든 Update 가 끝난 값을 읽어야 하면 IPostTickable 을 씁니다.
주의할 점
1. IStartable.Start() 를 유니티의 Start 로 읽지 않는다
안 지키면 생기는 문제. 진입점은 게임오브젝트에 붙어 있지 않아서 유니티가 부르는 것이 아닙니다. 스코프가 빌드를 끝낸 뒤 디스패처가 플레이어 루프에 꽂은 자리에서 불립니다. 모노비헤이비어의 Start 와 같은 순서, 같은 조건으로 돈다고 가정하면 순서가 어긋납니다.
이렇게 쓴다. 위 흐름도를 기준으로 고릅니다. 모노비헤이비어의 Start 뒤여야 하면 IPostStartable, Awake 안에서 곧바로 끝내야 하면 IInitializable 입니다.
2. 진단은 개발 중에만 켠다
안 지키면 생기는 문제. 설정 자산의 Enable Diagnostics 를 켜면 등록마다 스택 추적을 뜨고, Resolve 마다 진단 정보를 선형으로 훑고, 만든 인스턴스를 붙들고 있습니다. Singleton Resolve 1회가 Mono 에서 약 36배(0.13 µs → 4.6 µs), IL2CPP 에서 약 11배(0.27 µs → 2.8 µs) 느려지고 Resolve 마다 약 500 B 를 할당했습니다. Transient 는 Resolve 를 거듭할수록 느려져 10,000번이면 Mono 약 19배, IL2CPP 약 31배였습니다. 켠 채로 누수를 재면 진단이 쥔 참조 때문에 결과가 왜곡되고, Resolve 시간은 밀리초 정수라 1밀리초 아래는 0으로 찍힙니다.
이렇게 쓴다. 진단 창(Window > VContainer Diagnostics)은 등록과 계층을 확인할 때만 켜고, 릴리스 전과 측정할 때는 설정 자산에서 끕니다.
정리
VContainer 는 스코프의 Awake 안에서 컨테이너를 세우고, 진입점은 다음 프레임 플레이어 루프에서 부릅니다.
- 스코프(모노비헤이비어), 빌더(수집),
Resolver(생성) 셋이 등장인물이고, 스코프가 씬과 함께 죽으며 컨테이너를 해제합니다. Awake(순서-5000) →Configure→ 빌드 →Initialize까지가 스코프Awake안이고, 다른 스크립트Awake뒤 첫 프레임에IStartable.Start()→Start()→PostStart()순입니다.- 매 프레임은
Tick()→Update()→PostTick()처럼 진입점이 모노비헤이비어 콜백 앞에, Post 가 뒤에 옵니다. - 진단은
Resolve를 수십 배 느리게 하고 인스턴스를 붙듭니다. 개발 중에만 켭니다.