VitalRouter 는 자식 스코프에서도 라우터를 새로 만들지 않는다
Esc

    VitalRouter 는 자식 스코프에서도 라우터를 새로 만들지 않는다

    2026년 9월 25일 수정–UnityDI라이브러리메시징
    public class BattleLifetimeScope : LifetimeScope      // 씬마다 만들어지는 자식 스코프
    {
        protected override void Configure(IContainerBuilder builder)
        {
            builder.RegisterVitalRouter(routing =>
            {
                routing.Map<BattlePresenter>();
                routing.Filters.Add<LoggingInterceptor>();
            });
        }
    }

    씬 스코프에서 이렇게 등록하면 씬마다 라우터가 하나씩 생기고, 씬을 내리면 같이 사라질 것 같습니다. 그런데 씬을 몇 번 오가면 커맨드 하나에 로그가 여러 번 찍힙니다.

    VitalRouter 의 VContainer 연동은 부모 스코프에 Router 가 있으면 새로 만들지 않고 그것을 씁니다. 그리고 스코프를 닫을 때 라우트만 떼고 인터셉터는 떼지 않아서, 자식이 붙인 인터셉터가 부모 라우터에 쌓입니다.

    부모에 라우터가 있으면 그것을 쓴다

    // VitalRouter.Unity VContainerExtensions.cs (줄임)
    if (routing.Isolated || !builder.Exists(typeof(Router), findParentScopes: true))
        builder.Register<Router>(Lifetime.Singleton).AsImplementedInterfaces().AsSelf();
    조건 자식 스코프가 쓰는 라우터
    부모에 Router 가 없다 이 스코프에 새로 만든다
    부모에 있고 Isolated = false (기본) 부모의 라우터를 그대로 쓴다
    Isolated = true 이 스코프 전용 라우터를 새로 만든다

    기본값이 공유라서 씬 스코프의 Presenter 들은 루트의 라우터 하나를 탑니다. 스코프를 나눴다고 라우터가 나뉘지는 않습니다. 컨테이너가 다 만들어진 뒤에는 그 라우터에 AddFilter 로 인터셉터를 붙이고, Map<T>() 한 클래스의 생성된 MapTo 를 리플렉션으로 찾아 부릅니다.

    닫을 때 라우트만 떼고 인터셉터는 남긴다

    // 스코프가 만들어질 때
    foreach (var interceptorType in routing.Filters.Types)
        router.AddFilter((ICommandInterceptor)container.Resolve(interceptorType));
    
    // 스코프가 닫힐 때
    builder.RegisterDisposeCallback(container =>
    {
        for (var i = 0; i < routes.Count; i++)
            routes[i].UnmapRoutesMethod.Invoke(instance, null);   // 라우트만 뗀다. RemoveFilter 가 없다
    });

    붙이는 쪽과 떼는 쪽이 짝이 맞지 않습니다. routing.Ordering 도 결국 순서 제어 인터셉터를 붙이는 것이라 같습니다.

    자식 스코프를 다섯 번 만들고 닫으며, 닫은 뒤 발행 한 번에 인터셉터가 돈 횟수를 셌습니다.

    몇 번째 로드 1 2 3 4 5
    인터셉터가 돈 횟수 1 2 3 4 5

    Mono 와 IL2CPP 플레이어가 같았습니다. 닫힌 스코프가 만든 인스턴스를 계속 부르지만 예외는 나지 않아서, 깨지지 않고 조용히 쌓입니다.

    Isolated 를 켜면 부모에 아무것도 붙지 않는다

    routing.Isolated = true 를 켜면 이 스코프 전용 라우터를 새로 만들어 거기에 붙입니다. 같은 구성으로 세 번 불러온 뒤 부모로 발행하니 인터셉터가 한 번도 돌지 않았습니다.

    builder.RegisterVitalRouter(routing =>
    {
        routing.Isolated = true;
        routing.Map<BattlePresenter>();
        routing.Filters.Add<LoggingInterceptor>();
    });

    주의할 점

    1. 씬 스코프에서 인터셉터나 순서 제어를 쓰면 Isolated 를 켠다

    안 지키면 생기는 문제. 다섯 번 불러오면 발행 한 번에 로그가 다섯 번 찍히고, 권한 확인을 다섯 번 하고, 순서 제어가 다섯 겹이 됩니다. 스코프를 닫아도 줄지 않으니 오래 플레이할수록 발행이 무거워집니다.

    이렇게 쓴다. 셋 중 하나를 고릅니다. 씬 스코프에서 routing.Isolated = true 를 켜거나, 부모 라우터를 꼭 공유해야 하면 스코프를 닫을 때 직접 router.RemoveFilter(...) 를 부르거나, 인터셉터는 루트 스코프에서 한 번만 등록하고 자식 스코프는 Map 만 합니다.

    2. Isolated 로 나누면 커맨드를 어느 라우터로 보낼지 같이 정한다

    안 지키면 생기는 문제. Isolated 스코프의 Presenter 는 그 스코프 전용 라우터에 연결됩니다. 부모에서 Router 를 주입받아 발행하던 코드는 이제 그 Presenter 에 닿지 않고, 씬 안에서 발행한 커맨드도 부모 쪽 구독자에게 가지 않습니다.

    이렇게 쓴다. 씬 안에서만 오가는 커맨드와 씬을 넘나드는 커맨드를 나누고, 넘나드는 커맨드는 부모 라우터를 따로 주입받아 발행합니다.

    3. 씬에 있는 MonoBehaviour 는 Map 이 아니라 MapComponent 로 연결한다

    안 지키면 생기는 문제. Map<T>() 에 MonoBehaviour 를 넘기면 씬의 컴포넌트가 아니라 새 GameObject 에 붙인 것이 만들어져 라우터에 연결됩니다. 씬에 배치해 둔 Presenter 는 커맨드를 받지 못하고 보이지 않는 복제본이 대신 받습니다. [Routes] 를 빠뜨린 클래스를 Map 하면 컴파일은 되고 스코프를 만드는 순간 MapTo 가 없다는 예외가 납니다.

    이렇게 쓴다. 씬의 오브젝트는 MapComponent(instance) 나 MapComponentInHierarchy<T>() 로 연결하고, 연결하는 클래스는 [Routes] 를 붙인 partial class 로 만듭니다.

    정리

    VitalRouter 의 VContainer 연동은 자식 스코프에서도 부모의 라우터를 공유하고, 닫을 때 인터셉터를 떼지 않습니다.

    • Isolated 가 아니면 부모에 Router 가 있는 한 새로 만들지 않습니다.
    • 자식이 붙인 인터셉터는 스코프를 닫아도 남아, 씬을 불러온 횟수만큼 발행마다 돕니다.
    • Isolated = true 면 전용 라우터에 붙어 부모에는 아무것도 남지 않고, 대신 커맨드가 갈 라우터를 나눠 정해야 합니다.