Skip to content

❗ 테이블 뷰가 보고 있는 배열과 bind하고 있는 배열 간의 race condition 문제

Tltlbo edited this page Dec 4, 2024 · 2 revisions

UIKit에서 테이블 뷰는 일일히 업데이트 해주어야 한다.

UIKit에서 테이블 뷰는 DataSource가 변하더라도 뷰가 업데이트 되지 않는다.

그러므로, 일일히 업데이트 해주어야 한다.

tableView.reloadData()

이런 식으로 테이블 뷰가 보고 있는 DataSource의 변화를 모두 reload해주는 함수이다.

하지만 reloadData는 기능 부하가 너무 크다.

reloadData는 뷰를 아예 새로 그려버리기 때문에 성능적 부하가 크다.

셀 1개만 바뀌어도 모든 셀을 다시 그려버릴 순 없지 않은가?

새로 추가된 정보의 셀만 추가하자.

viewModel.$currentRecords
            .receive(on: DispatchQueue.main)
            .sink { [weak self] records in
                guard let self, !records.isEmpty else { return }
                let indexPath = IndexPath(row: records.count - 1, section: 0)
                self.resultTableView.insertRows(at: [indexPath], with: .fade)

이런 식으로 record가 변화할 때마다 추가 된 것이므로, 테이블 뷰에 row 자체를 추가하게 만들어주었다.

Error 발생

이미 추가된 셀에 insert할 수 없습니다 라는 느낌의 오류를 받게 되었다.

tableView의 row 생성 시점과 insertRows하는 시점의 raceCondition 문제

테이블 뷰에서 currentRecords 변화를 알고 row를 생성하는 시점과 currentRecords를 bind해서 insertRows하는 시점 간에

테이블 뷰에서 먼저 row를 생성하게 되면 insertRows는 이미 들어간 row를 접근하게 되기 때문에 생기는 문제이다.

방어 로직을 만들자.

viewModel.$currentRecords
            .receive(on: DispatchQueue.main)
            .sink { [weak self] records in
                guard let self, !records.isEmpty else { return }
                let indexPath = IndexPath(row: records.count - 1, section: 0)
                if records.count > resultTableView.numberOfRows(inSection: 0) {
                    resultTableView.insertRows(at: [indexPath], with: .fade)
                }
                else {
                    resultTableView.reloadRows(at: [indexPath], with: .fade)
                }
            }
            .store(in: &cancellables)

먼저 테이블 뷰의 row 개수 여부를 확인하고 currentRecords 개수와 비교해서

테이블 뷰의 row개수가 더 적다면 insertRows.

row가 이미 있다면 reloadRows로 방어 로직을 작성해주었다.

이렇게 하면 이미 row가 있다면 insert와 같은 raceCondition 문제를 막을 수 있지... 않다...

왜?

아주 낮은 확률이나, resultTableView의 Row개수가 records의 개수랑 비교했을 때 작다는 걸 확인한 그 순간에

Row가 생성되어 Data-Race가 발생하게 된다.

그로 인해 해당 Row 자리에는 이미 Row가 있어 insertRows시 에러가 발생하게 된다.

DiffableDataSource

DiffableDataSource를 이용하면 된다.

간단히 말하자면, snapShot을 통해 들어온 데이터로 테이블 뷰를 구성하고,

이후에 snapShot으로 새로운 데이터가 들어왔을 때, 이전에 찍어주었던 snapShot과 비교하여 새로 들어오거나 변화된 데이터에 대해서만 reload를 진행한다.

마치 reloadData와 동일하게 동작하나, 효율적으로 움직인다고 볼 수 있다.

iOS07 프로젝트 일지

📚 문서

🫶🏻 팀 기록

🎤 프로젝트

💡 핵심 경험

🚨 트러블 슈팅

📔 학습 정리

🪄 QA

Clone this wiki locally