VContainer 스코프는 부모를 못 찾으면 조용히 기다린다
Esc

    VContainer 스코프는 부모를 못 찾으면 조용히 기다린다

    2026년 9월 30일 수정–UnityDI라이브러리
    // Battle 스코프의 부모는 Common 씬에 있다
    await SceneManager.LoadSceneAsync("Battle", LoadSceneMode.Additive);
    await SceneManager.LoadSceneAsync("Common", LoadSceneMode.Additive);

    로드 순서가 뒤집혔는데 예외도 경고도 없습니다. 결국 붙기는 하지만, 그 전에 Battle 씬 컴포넌트의 Awake 와 Start 가 주입 없이 지나갑니다.

    VContainer 스코프는 부모를 정해진 여섯 단계로 찾고, 못 찾으면 로그 없이 대기열에 누웠다가 부모가 빌드를 끝낼 때 깨어납니다. 기다리는 동안 그 스코프의 컨테이너는 널입니다.

    루트는 설정 자산이 씬보다 먼저 세운다

    부모 탐색의 마지막 단계가 설정 자산의 루트입니다. Assets > Create > VContainer > VContainer Settings 로 만든 자산은 사전 로드 자산에 자동 등록되어 씬보다 먼저 로드되고, 그 OnEnable 이 부팅 훅이 됩니다.

    void OnEnable()
    {
        if (Application.isPlaying)
        {
            Instance = this;
            var activeScene = SceneManager.GetActiveScene();
            if (activeScene.isLoaded)
                OnFirstSceneLoaded(activeScene, default);        // 이미 로드됐다
            else
                SceneManager.sceneLoaded += OnFirstSceneLoaded;  // 로드를 기다린다
        }
    }

    Root LifetimeScope 에 지정한 프리팹은 첫 씬 로드 때 꺼진 채로 복제되고 DontDestroyOnLoad 가 붙은 뒤 켜집니다. 복제 직후 도는 Awake 를 준비가 끝난 뒤로 미루려는 순서입니다.

    부모는 여섯 단계 중 먼저 걸린 것이다

    LifetimeScope GetRuntimeParent()
    {
        if (IsRoot) return null;                                               // 1. 루트
        if (parentReference.Object != null) return parentReference.Object;    // 2. 인스턴스 직접 지정
        var implParent = FindParent();                                         // 3. FindParent 재정의
        if (implParent != null) return implParent;
        if (parentReference.Type != null && parentReference.Type != GetType()) // 4. 인스펙터 타입
        {
            var found = Find(parentReference.Type);
            if (found != null && found.Container != null) return found;
            throw new VContainerParentTypeReferenceNotFound(parentReference.Type, ...);
        }
        if (GlobalOverrideParents.Count > 0) return GlobalOverrideParents.Peek(); // 5. EnqueueParent
        if (VContainerSettings.Instance != null)                               // 6. 설정 자산 루트
            return VContainerSettings.Instance.GetOrCreateRootLifetimeScopeInstance();
        return null;
    }

    가장 흔한 경로인 인스펙터 타입이 어떻게 판정되는지 걸어 봅니다.

    4단계는 오브젝트가 있느냐가 아니라 빌드가 끝났느냐(Container != null)를 봅니다. 탐색은 FindAnyObjectByType 이라 비활성 오브젝트는 아예 못 찾습니다. 아무것도 지정하지 않으면 설정 자산의 루트가 부모가 되고, 설정 자산이 없으면 독립 컨테이너가 됩니다.

    못 찾으면 대기열에서 로그 없이 기다린다

    4단계의 예외는 Awake 가 받아서 대기열로 보냅니다.

    catch (VContainerParentTypeReferenceNotFound) when (!IsRoot)
    {
        if (WaitingList.Contains(this)) throw;   // 대기열에 든 채로 또 실패하면 예외
        EnqueueAwake(this);                       // 아니면 대기열에 넣고 조용히 끝낸다
    }

    깨어나는 길은 둘입니다. 부모가 빌드를 끝냈을 때(인스펙터 부모 타입이 부모의 실제 타입과 정확히 같은 대기자만)와 자기 씬 로드가 끝났을 때입니다. 두 길 모두 대기열에서 뺀 다음 Awake 를 다시 부르므로, 다시 실패해도 예외 대신 대기열로 돌아갑니다. 부모가 끝내 안 오면 끝까지 조용합니다.

    맨 위 순서로 직접 로드했습니다. Battle 씬에는 RegisterComponentInHierarchy 로 주입받는 소비자를 두고, 스코프와 소비자가 불릴 때마다 로그를 찍었습니다.

    Battle 로드 30프레임 뒤에도 컨테이너는 널이었고, 그사이 경고나 예외는 한 줄도 없었습니다. Common 을 먼저 로드한 대조군은 대기열을 거치지 않고 소비자가 자기 Awake 전에 주입받았습니다. 붙기는 하지만, 붙기 전에 소비자의 Awake 와 Start 가 빈손으로 지나갑니다.

    주의할 점

    1. 애디티브 씬은 EnqueueParent 로 부모를 명시한다

    안 지키면 생기는 문제. 인스펙터 Parent 에만 기대면 로드 순서에 따라 자식이 대기열을 거치고, 그사이 자식 씬 컴포넌트가 주입 없이 돕니다. 한 씬에 부모를 지정하지 않은 스코프를 둘 두면 둘 다 설정 자산 루트나 독립 컨테이너 밑에 서서 형제가 되고, 등록이 갈라져 서로 못 봅니다.

    이렇게 쓴다. 자식 씬을 로드하는 코드를 감쌉니다. 5단계에서 바로 걸려 씬 탐색을 건너뜁니다.

    using (LifetimeScope.EnqueueParent(commonScope))
    {
        await SceneManager.LoadSceneAsync("Battle", LoadSceneMode.Additive);
    }

    2. 부모 스코프를 꺼 두지 않는다

    안 지키면 생기는 문제. 인스펙터 타입 탐색은 비활성 오브젝트를 못 찾습니다. 부모가 꺼져 있으면 자식은 대기열로 가서 조용히 기다립니다.

    이렇게 쓴다. 부모 스코프가 붙은 게임오브젝트는 켜 둔 채 둡니다. 부모를 늦게 켜야 한다면 자식도 그 뒤에 만듭니다.

    3. FindParent() 재정의만으로 찾는 스코프는 부모 빌드가 깨워 주지 않는다

    안 지키면 생기는 문제. 부모 빌드가 대기열을 깨우는 기준은 인스펙터 부모 타입의 정확한 일치뿐입니다. FindParent() 만 재정의하고 인스펙터 타입을 비워 두면, 부모가 없을 때 널이 돌아와도 예외가 없어 대기열에 들어가지 않고 부모 없이(설정 자산 루트가 있으면 그 밑에) 빌드됩니다. 나중에 부모가 로드돼도 다시 붙지 않습니다. 인스펙터에 부모의 기반 클래스를 적어도 깨워지지 않습니다.

    이렇게 쓴다. 부모가 먼저 떠 있는 순서로 로드하거나, 인스펙터에 부모의 실제 타입을 정확히 적거나, EnqueueParent 로 명시합니다.

    4. 대기 중인 스코프는 직접 찾아낸다

    안 지키면 생기는 문제. 대기열은 정적 컬렉션이라 부모를 영영 안 만들면 그 스코프는 Container 가 널인 채 방치됩니다. 진입점이 안 돌고 주입받을 코드는 빈손인데, 프레임워크는 경고를 내지 않습니다.

    이렇게 쓴다. 인스펙터에 부모 타입을 지정했다면 개발 중에 Container 가 널인 스코프가 없는지 검사하는 코드를 둡니다.

    정리

    VContainer 스코프는 부모를 여섯 단계로 찾고, 못 찾으면 조용히 기다립니다.

    • 설정 자산이 사전 로드 자산으로 부팅 훅이 되어, 지정이 없을 때의 부모인 루트를 씬보다 먼저 세웁니다.
    • 인스펙터 타입 탐색은 비활성 오브젝트를 못 찾고, 빌드가 끝난 부모만 인정합니다.
    • 대기 중에는 로그도 예외도 없고, 자식 씬 컴포넌트는 주입 없이 돕니다. 애디티브 씬은 EnqueueParent 로 명시합니다.