VContainer 의 CreateScope 스코프는 씬이 바뀌어도 닫히지 않는다
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으로 직접 닫습니다.