VContainer 의 CreateScope 스코프는 씬이 바뀌어도 닫히지 않는다
Esc

    VContainer 의 CreateScope 스코프는 씬이 바뀌어도 닫히지 않는다

    2026년 9월 30일 수정–UnityDI라이브러리
    var child = container.CreateScope(builder =>
    {
        builder.Register<StageData>(Lifetime.Scoped);
    });
    
    SceneManager.LoadScene("Title");

    씬을 갈아엎었으니 StageData 도 정리됐을 것 같습니다. 재 보면 아닙니다.

    시점 StageData 해제됐나 비고
    씬 전환 뒤 아니오 다시 Resolve 하면 같은 인스턴스, 지금까지 만든 수 1
    child.Dispose() 뒤 예 만든 쪽이 직접 닫아야 해제된다
    parent.Dispose() 뒤 (child 는 안 닫음) 아니오 부모를 닫아도 CreateScope 로 만든 자식은 같이 안 닫힌다

    CreateScope 로 만든 스코프는 씬이 바뀌어도 그대로 살아 있고, 그 안의 객체도 해제되지 않습니다. 누군가 Dispose() 를 부를 때까지 갑니다.

    자식 스코프를 만드는 방법은 둘이고, 차이는 스코프가 게임오브젝트냐 아니냐 하나입니다. VContainer 의 해제는 스코프의 게임오브젝트가 파괴되는 데서 시작하기 때문입니다.

    CreateChild (게임오브젝트 스코프) container.CreateScope (C# 스코프)
    정체 게임오브젝트에 붙은 LifetimeScope 순수 C# 객체
    씬이 바뀌면 게임오브젝트와 함께 파괴되고, 안의 객체가 해제된다 그대로 살아 있다
    부모 스코프가 없어지면 부모 게임오브젝트 밑에 붙어 있어 같이 파괴된다 살아 있다
    누가 닫나 유니티 (게임오브젝트 파괴) 만든 코드가 Dispose()
    이럴 때 쓴다 씬, 전투처럼 유니티 오브젝트와 같이 사라질 것 씬과 상관없이 직접 열고 닫는 작업 단위 (using 으로 감쌀 수 있는 것)

    정리하면, 씬이나 전투 단위의 스코프는 CreateChild 로 만들고, CreateScope 는 닫는 코드를 직접 쓸 때만 씁니다.

    계층은 수명이 같은 것끼리 묶는다

    자식은 부모의 등록을 보고, 부모는 자식을 못 봅니다. 그래서 계층은 수명이 같은 것끼리 묶습니다.

    깊이는 셋이면 충분합니다. 더 깊으면 조회가 계층 수만큼 위로 올라가고, 무엇이 어디 등록됐는지 사람이 못 따라갑니다.

    CreateChild 는 게임오브젝트, CreateScope 는 순수 C# 객체다

    자식 스코프를 만드는 방법이 둘이고, 죽는 조건이 다릅니다.

    CreateScope 가 잘못된 것은 아닙니다. 씬과 무관한 수명을 직접 관리할 때 쓰라고 있는 것이고, 관리 책임이 호출부로 넘어온다는 뜻입니다.

    씬 전환의 해제는 게임오브젝트 파괴를 타고 내려간다

    스코프가 MonoBehaviour 라 OnDestroy 에서 DisposeCore() 가 돌고, 컨테이너가 안쪽 스코프부터 해제 목록을 비웁니다. 루트 스코프는 DontDestroyOnLoad 라 씬 전환에 걸리지 않습니다. 게임오브젝트가 아닌 것은 이 체인의 시작점이 없습니다.

    주의할 점

    1. 전부 루트에 등록하지 않는다

    안 지키면 생기는 문제. 루트는 씬 전환에 안 걸리므로 씬을 넘겨도 상태가 남습니다. 컨테이너를 쓰는 전역 싱글턴 모음이 되고, 씬을 다시 열어도 지난 판의 값으로 시작합니다.

    이렇게 쓴다. 앱 내내 살 것은 루트에, 씬마다 초기화할 것은 씬 스코프에, 세션마다 새로 시작할 것은 그 아래에 둡니다.

    2. CreateScope 로 만든 스코프는 만든 쪽이 닫는다

    안 지키면 생기는 문제. 게임오브젝트가 아니고 부모의 해제 목록에도 없어서, 씬 전환도 부모 해제도 닫아 주지 않습니다. 전투마다 새로 만들면 스코프가 쌓이고, 그 안의 진입점이 쌓인 만큼 겹쳐 돕니다.

    이렇게 쓴다. 참조를 들고 있다가 다음 것을 만들기 전에 Dispose() 를 부르거나 using 으로 감쌉니다. 씬과 같이 죽어야 하면 CreateChild 를 씁니다.

    3. 다음 씬 스코프에 넘기는 Enqueue 는 using 으로 감싼다

    안 지키면 생기는 문제. 다음 씬의 스코프에 등록을 예약하는 LifetimeScope.Enqueue 는 정적 변수 없이 값을 넘기는 가장 안전한 길이지만, 예약이 안 걷히면 그 뒤에 만들어지는 모든 스코프에 같은 등록이 따라 붙습니다.

    이렇게 쓴다. 씬 로드 한 번을 using 블록 하나로 감쌉니다. 블록을 벗어나는 순간 예약이 걷힙니다.

    using (LifetimeScope.Enqueue(builder => builder.RegisterInstance(stage)))
    {
        await SceneManager.LoadSceneAsync("Battle");
    }

    정리

    VContainer 스코프는 게임오브젝트일 때만 씬과 함께 닫힙니다.

    • 자식은 부모를 보고 부모는 자식을 못 보므로, 수명이 같은 것끼리 셋 이내의 계층으로 묶습니다.
    • CreateChild 는 스코프 밑에 게임오브젝트를 만들어 같이 파괴되고, CreateScope 는 부모가 죽어도 삽니다.
    • 해제는 OnDestroy 에서 시작하므로, 순수 C# 스코프는 Dispose() 나 using 으로 직접 닫습니다.