ForEach에서 Binding으로 하위 View에 데이터 전달할 때 문제점

SteadySlower·2022년 8월 20일
0

문제 현상

아무것도 안하고 그냥 성공 ↔  실패 드래그만 했는데 갑자기 ForEach 안에 있는 Cell들이 전부 reinit 되는 현상이 있었습니다. 성공 ↔  실패의 경우는 Cell 내부의 property를 변경하는 것이기 때문에 의도된 대로 동작을 한다면 그냥 Cell 하나만 reinit되어야 하는데요. 이런 이슈가 왜 발생했는지 알아보겠습니다.

원인

원인은 상위 View에 @Published로 선언된 배열의 원소가 하위 View에서 Binding으로 연결되어 있는 구조 때문이었습니다. 하위 View에서 wordDisplay가 변경되면 단순히 하위 View에서의 변경에 그치는 것이 아니라 상위 View의 데이터인 배열 역시 변경되는 것이죠.

이렇게 되면 @Published로 선언되어 있기 때문에 연결된 상위 View(ForEach 부분)가 전부 re-render됩니다. 따라서 Cell 1개만 re-init되는 것이 아니라 모든 Cell이 re-init되는 것이죠.

// 상위 View
ForEach(0..<viewModel.displays.count, id: \.self) { index in
    WordCell(wordDisplay: $viewModel.displays[index], eventPublisher: viewModel.eventPublisher)
}

// 상위 View의 ViewModel
@Published var displays: [WordDisplay] = []
// 하위 View의 ViewModel
@Binding var wordDisplay: WordDisplay

// 드래그하면 실행되는 메소드
func updateStudyState(to state: StudyState) {
    guard wordDisplay.word.studyState != state else { return }
    wordDisplay.word.studyState = state
}

우려되는 문제점

Cell이 init될 때 사진 prefetch 해도록 구현해놓았는데요. 이 경우에 괜히 쓸 떼 없이 이 메소드가 호출이 될 수 있습니다. (물론 제가 사용한 라이브러리인 Kingfisher는 해쉬테이블을 사용해서 캐싱하기 때문에 문제는 없었습니다.) 만약에 기능 추가로 네트워크 호출이 추가될 경우 성능 문제가 발생할 수 있습니다.

무엇보다 중요한 것은 우리가 의도한 작동방식이 아니라는 점입니다.

마치며…

물론 Binding을 사용하는 방법이 무조건적으로 틀린 방법이라고 볼 수는 없습니다. 하지만 이런 부작용이 있으니 Binding을 사용하는 이득이 부작용보다 클때만 사용해야 합니다!

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글