상세 컨텐츠

본문 제목

TMDB에서 생긴일 - 2

개발일기

by kimrindev 2025. 5. 24. 00:29

본문

왜 Combine을 쓰게 됐을까? 그리고 그 선택은 나에게 어떤 문제를 던졌는가

 

앱을 리팩토링하면서 가장 먼저 떠올랐던 고민은 “데이터 흐름을 어떻게 깔끔하게 정리할 수 있을까?” 였다.

기존에는 RxSwift를 써봤던 경험이 있어서 흐름 자체는 익숙했지만, 이번 프로젝트에서는 Combine을 처음으로 써보고 싶었다.

 

가장 큰 이유는 Swift에서 공식적으로 제공하는 프레임워크이기도 하고, Apple 생태계 안에서 좀 더 자연스럽게 녹아드는 구조를 경험해보고 싶었기 때문이다.

 

내가 구성한 구조는 꽤 단순하고 이상적이었다.

  • TMDB API를 호출해 데이터를 ViewModel에서 받아오고
  • @Published로 선언한 배열에 데이터를 넣고
  • ViewController에서는 Combine으로 .sink를 걸어주고
  • 데이터가 들어오는 순간 collectionView.reloadData()를 호출하는 구조

 

실제로도 처음엔 잘 동작했다. Combine의 흐름을 따라 UI가 자연스럽게 업데이트되고, 예상한 대로 화면이 그려지는 걸 보면서

“이거 진짜 괜찮은데?” 싶었다.

하지만 문제는 그렇게 쉽게 끝나지 않았다. 앱을 조금만 더 테스트하다 보면 특정 시점에서 앱이 갑자기 죽어버리곤 했다.

 

Invalid number of items in section 0. The number of items in the collection view after the update (3) must be equal to the number of items before the update (0) plus or minus the number of items inserted or deleted (0).

 

 

처음에는 단순히 reload 시점이 어긋난 건가 싶었다.

“뭐, sink가 먼저 반응했나 보지” 정도로 가볍게 생각했는데, 문제를 쫓아가다 보니 구조적인 문제라는 걸 깨닫게 됐다.

 

내가 처음에 설계했던 방식은 다음과 같았다.

 

영화 데이터를 상영 중 / 개봉 예정 / 인기 순으로 세 가지 섹션으로 나누고, 각 섹션의 데이터를 각각 Combine .sink로 바인딩해서

각 섹션마다 reloadSections()을 따로 호출하는 구조였다.

각 데이터가 비동기적으로 들어올 수 있고, Combine은 그걸 자동으로 감지해서 뷰를 업데이트해주니까

이론적으로는 효율적이고 완벽하게 생각했다. 

 

근데 UICollectionView는 내부적으로 셀의 상태나 인덱스를 캐시하고 있어서, 부분 reload가 순차적으로 일어나거나 겹쳐서 발생하면 내부 상태가 꼬이고, 결국 크래시를 유발하게 된다. 

 

 

특히 Combine은 비동기 스트림이다 보니까 데이터가 들어오는 타이밍이 정확히 보장되지 않아서 이런 문제가 훨씬 쉽게 발생할 수 있었다.

 

이걸 해결하기 위해서 나는 구조를 전면 수정했다. 각각의 sink를 따로 관리하던 방식에서 벗어나,

CombineLatest3를 사용해서 세 데이터가 모두 들어온 순간에만 reloadData()를 호출하도록 바꿨다.

 

private func bindViewModel() {
    Publishers.CombineLatest3(
        viewModel.$nowPlaying,
        viewModel.$upcoming,
        viewModel.$popular
    )
    .receive(on: RunLoop.main)
    .sink { [weak self] _, _, _ in
        self?.collectionView.reloadData()
    }
    .store(in: &cancellables)
}

 

이렇게 바꾸고 나니까 충돌은 더 이상 발생하지 않았고, UI도 예상한 대로 안정적으로 동작하게 되었다.

 

 

단순히 sink로 받아서 reload하면 끝이라는 식으로 접근하면 UI와 데이터 사이의 타이밍 충돌을 막을 수 없다.

특히 UICollectionView처럼 상태에 민감한 UI 컴포넌트랑 사용할 때는 정확한 데이터 흐름의 순서와 일관성을 먼저 설계한 다음에 Combine을 얹어야 한다는 걸 배웠다.

 

 

 

 

 

 

 

 

관련글 더보기