Cheondi
개발 · Unity

창을 닫는 순서가 버그를 만들었다

화면 닫기와 데이터 갱신 이벤트가 엇갈리며 이전 창이 다시 살아나던 문제를 순서대로 추적한 기록입니다.

  • #unity
  • #events
  • #lifecycle

상세 차트를 연 상태에서 계좌를 바꾸면 창이 닫혀야 했다. 대부분은 잘 닫혔지만 특정 순서로 조작하면 차트가 다시 나타났다. 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() 한 줄에 있지 않았다. 닫기와 갱신은 각각 맞았지만 둘의 순서가 틀렸다. 각 함수가 혼자 올바른 것과 전체 흐름이 올바른 것은 다르다는 걸 배운 작업이었다.