상세 차트를 연 상태에서 계좌를 바꾸면 창이 닫혀야 했다. 대부분은 잘 닫혔지만 특정 순서로 조작하면 차트가 다시 나타났다. SetActive(false)가 호출되는 로그까지 확인했는데 화면에는 여전히 창이 보였다. 처음에는 Unity가 비활성화를 늦게 처리한다고 의심했다.
실제 원인은 닫기 호출 뒤에 도착한 갱신 이벤트였다. 창은 정상적으로 닫혔지만 이전 요청의 결과를 처리하는 코드가 상세 창을 다시 열고 있었다. 마지막 화면만 보고 있으면 “닫히지 않았다”로 보였지만, 내부에서는 닫혔다가 다시 열린 것이었다.
최종 결과에서 누락된 중간 과정
처음 로그는 함수 이름만 찍고 있었다.
CloseDetail
RefreshDetail
두 줄만으로는 어떤 계좌와 요청에 대한 호출인지 알 수 없었다. 요청 번호와 선택된 계좌, 프레임을 같이 남기자 이전 계좌의 응답이 뒤늦게 도착한다는 게 보였다.
Debug.Log($"detail response request={requestId}, account={accountId}");
이때부터 로그를 많이 찍는 것보다 서로 이어지는 값을 같이 찍는 게 중요하다고 느꼈다.
지연 응답 폐기 기준
요청을 취소할 수 있으면 좋겠지만 모든 호출이 그렇게 만들어져 있지는 않았다. 대신 응답을 적용하기 전에 현재 화면이 여전히 그 요청을 기다리는지 확인할 수 있었다.
int activeRequestId;
void RequestDetail()
{
activeRequestId++;
Send(activeRequestId);
}
void ApplyDetail(int requestId, Detail data)
{
if (requestId != activeRequestId)
return;
Render(data);
}
계좌나 화면이 바뀌면 요청 식별자가 달라지고, 이전 결과는 도착하더라도 화면을 건드리지 않는다. 단순한 예시지만 당시 문제를 이해하는 데 가장 도움이 된 방식이었다.
닫기 이벤트의 발생 근거
사용자가 직접 닫은 경우와 계좌 전환 때문에 닫힌 경우는 이후 행동이 달랐다. 사용자가 닫았는데 자동 갱신이 다시 열어서는 안 됐고, 잠깐 새로고침하는 과정이라면 선택 상태를 유지할 수도 있었다.
그래서 Close()라는 결과만 전달하기보다 UserClosed, AccountChanged, ViewDisposed 같은 이유를 구분해 보게 됐다. 모든 프로젝트에 같은 enum이 필요한 건 아니지만, 닫힌 이유가 다음 상태를 결정한다면 정보가 사라지지 않게 해야 했다.
화면 오류의 시간 순서 분석
이전까지는 화면이 이상하면 현재 오브젝트의 값만 확인했다. 이 문제 이후에는 바로 직전에 어떤 이벤트가 있었고, 어떤 비동기 결과가 나중에 도착했는지 시간 순서로 본다. 화면 녹화와 로그 시간을 같이 비교하는 습관도 이때 생겼다.
버그는 CloseDetail() 한 줄에 있지 않았다. 닫기와 갱신은 각각 맞았지만 둘의 순서가 틀렸다. 각 함수가 혼자 올바른 것과 전체 흐름이 올바른 것은 다르다는 걸 배운 작업이었다.
추가 구현 기준
이 문제를 고친 뒤 관련 공식 자료를 찾아 읽으면서, 당시 코드에서 우연히 맞았던 부분과 규칙으로 남겨야 할 부분을 나눠 봤다.
Unity 실행 순서 문서는 Awake, OnEnable, Start, Update 같은 엔진 이벤트가 어느 구간에서 호출되는지 설명한다. 하지만 로딩·성공·빈 결과·실패 같은 업무 상태의 순서까지 대신 정해 주지는 않는다. 두 종류의 순서를 섞어 생각하면 콜백이 한 프레임 늦게 왔다는 이유만으로 이전 화면 상태가 다시 살아날 수 있다.
그래서 수명주기 메서드는 상태 전이를 직접 흩어 놓는 장소보다, 한 곳에 모인 전이 함수를 호출하는 경계로 쓰는 편이 추적하기 쉬웠다.
다음에 화면 종료 이벤트의 실행 순서 같은 문제를 보면 아래 순서부터 확인하려고 한다.
- OnDisable·OnDestroy 중 실제 정리 시점
- 구독 해제와 취소 토큰의 소유자
- 닫힌 뒤 도착한 콜백 처리
- 다시 열었을 때 중복 등록 여부
새 응답을 적용하기 전에 요청 식별자와 현재 선택 조건이 여전히 같은지 확인해야 오래된 응답을 버릴 수 있다.
공식 참고 자료
- Unity 2021.3 이벤트 함수 실행 순서 — MonoBehaviour 이벤트와 프레임 단계의 실행 순서
- ECMA-334 C# 언어 사양 6판 — decimal·enum·예외를 포함한 C# 언어 규칙