이 포스팅을 쓰면서 어떤 제목을 붙여야 될까 고민을 했는데 고민 끝에 나온 제목이 저거 입니다… 제목이 역대급으로 기네요ㅠㅠ 요지는 간단한데 말이죠… 한번 풀어가 보겠습니다.
이 포스팅에서 하위 View의 상태를 바꿀 때 상위 View까지 전부 re-render되는 현상과 이유를 설명했는데요. 저는 이 상황이 싫어서 하위 View가 바뀔 때 상위 View가 re-render되지 않도록 아래와 같이 코드를 수정했습니다.
별도의 rawWord라는 배열을 두고 words 하위 View에서 단어의 상태가 수정이 되면 @Published가 아닌 변수인 rawWords의 상태만을 변경하는 것이죠. 이렇게 하면 상위 View가 re-render 되는 현상을 막을 수 있었습니다.
// 상위 View의 ViewModel
private var rawWords: [Word] = []
@Published var words: [Word] = []
// 하위 View에서 Word 객체의 상태를 변경하면 실행되는 함수
//👉 @Published 변수는 수정하지 않는다.
func updateStudyState(word: Word, state: StudyState) {
wordService.updateStudyState(word: word, newState: state) { [weak self] error in
// FIXME: handle error
if let error = error { print(error); return }
guard let self = self else { return }
// rawWords만 수정한다.
// words까지 수정하면 전체 list가 re-render되므로 낭비 (어차피 cell color는 WordCell 객체가 처리하니까)
guard let rawIndex = self.rawWords.firstIndex(where: { $0.id == word.id }) else { return }
self.rawWords[rawIndex].studyState = state
// 다만 틀린 단어만 모아볼 때이고 state가 success일 때는 View에서 제거해야하니까 filtering해서 words에 반영해야 한다.
if self.studyMode == .excludeSuccess && state == .success {
self.filterWords()
}
}
}
하지만 이렇게 하는 경우 의도치 않게 상위 View가 re-render 되는 경우 하위 View에서 바꾼 Word 객체의 상태가 상위 뷰에 반영되지 않는 경우가 있었습니다. 상위 View가 re-init되면 @Published인 words를 참조해서 View를 다시 그리는데 현재 rawWords에만 상태가 반영되어 있을 뿐 words에는 정작 이 상태의 변경이 없기 때문이죠. 이 포스팅에서 설명한 예시가 바로 그런 경우 중에 하나입니다. 이런 케이스 뿐만 아니라 상위 View 위에 Modal을 띄웠다가 없애는 경우 등 다양한 경우에 버그가 발생했습니다.
더 무서운 것은 언제 또 이러한 버그가 추가적으로 발생할지 모르는 것이죠.😱 제가 View가 re-render 되는 모든 케이스를 알고 있는 것도 아니고 혹여 알고 있다고 해도 나중에 SwiftUI가 업데이트 되면 어떻게 변할지 모르는 것이니까요.
결국 지금 같은 방법은 불안정하다는 결론을 내리고 하위 View에서 상태가 변경이 되면 상위 View의 @Published도 변경되도록 아래와 같이 수정을 했습니다.
// @Published 함수는 그대로 두고
@Published var words: [Word] = []
// 하위 View에서 상태가 변경될 때 @Published 변수를 직접 수정한다.
func updateStudyState(word: Word, state: StudyState) {
wordService.updateStudyState(word: word, newState: state) { [weak self] error in
// FIXME: handle error
if let error = error { print(error); return }
guard let self = self else { return }
guard let index = self.words.firstIndex(where: { $0.id == word.id }) else { return }
self.words[index].studyState = state
}
}
제가 처음에 @Published를 수정하는 것을 망설였던 이유는 성능 이슈였습니다. 하지만 Cell은 많아 봤자 100개 정도입니다. (저는 하루에 50개씩 단어를 외웁니다만, 좀 많이 외우시는 분도 있겠죠?) 1개를 re-render하나 100개를 re-render 하나 큰 차이가 없을 것 같습니다ㅎㅎ