iOS 개발자가 많이 하는 실수 - indexPath.row를 모델 인덱스로 과신해 데이터 소스 변경 시 잘못된 셀을 업데이트하는 실수
Dev Study/iOS 2025. 12. 4. 20:43반응형
iOS 개발자가 많이 하는 실수 - indexPath.row를 모델 인덱스로 과신해 데이터 소스 변경 시 잘못된 셀을 업데이트하는 실수
iOS에서 UITableView / UICollectionView를 사용할 때
가장 흔하고 위험한 실수 중 하나는 다음 패턴입니다.
“셀을 업데이트하려면 indexPath.row로 모델에 접근하면 되겠지?”
이 방식은 데이터 소스가 변하지 않는 단순한 구조에서는 문제 없이 보이지만,
현실적인 앱 환경에서는 다음과 같은 상황에서 쉽게 버그가 발생합니다.
- 섹션 추가/삭제
- filtering / sorting / searching
- 비동기 데이터 로딩
- diffable data source 사용
- 데이터 소스가 동적으로 변하는 리스트
- 셀 재사용 타이밍 변경
이 문제는 “어떤 셀에 어떤 데이터가 들어가야 하는지” 가
indexPath.row만으로는 절대 보장되지 않는다는 데서 출발합니다.
1. 문제 패턴: indexPath.row로 직접 모델 접근
예:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = items[indexPath.row] // ❌ 위험한 접근
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.reuseID, for: indexPath) as! MyCell
cell.configure(with: model)
return cell
}
이렇게 작성하는 경우:
- items 배열이 “셀 업데이트 전/후”에 달라져 있으면
indexPath.row에 해당하는 모델이 실제로 표시되어야 하는 모델과 다를 수 있음 - 특히 filtering, sorting, insertion, deletion이 자주 일어나는 상황에서
셀에 잘못된 데이터가 표시되는 문제로 이어짐
2. 문제의 원인: indexPath는 “그 시점의 위치”일 뿐
indexPath.row는 해당 셀이 그 순간 어떤 위치에 있는지를 나타낼 뿐,
그 셀과 연결된 모델을 “영구적/고정적으로” 보장하지 않습니다.
즉, 다음과 같은 상황이 발생할 수 있습니다.
- 셀 생성 시 indexPath.row == 3
- 데이터가 로딩되면서 items 배열이 변경
- 셀이 재사용되며 configure가 다시 호출되기 전에 indexPath가 달라짐
- index 3의 데이터는 완전히 다른 아이템이 됨
- 셀에는 “예전 데이터”가 잘못 표시되거나 섞여 보임
3. 특히 비동기 작업에서 치명적
func configure(at indexPath: IndexPath) {
let model = items[indexPath.row]
loadImage(model.url) { image in
self.thumbnail.image = image // ❌ 이 cell은 이제 indexPath.row의 셀이 아닐 수 있음
}
}
비동기 로딩이 늦게 끝나면:
- 셀 재사용이 일어나 indexPath가 다른 셀에 매칭
- 이미 재사용된 셀에 잘못된 이미지가 표시됨
4. 올바른 해결 방식
방법 1. 셀에 “모델 식별자”를 저장하고 검증
class MyCell: UITableViewCell {
private var currentID: String?
func configure(with model: Model) {
currentID = model.id
titleLabel.text = model.title
loadImage(from: model.imageURL) { [weak self] image in
guard let self, self.currentID == model.id else { return } // ✔️ 올바른 셀인지 확인
self.thumbnailImageView.image = image
}
}
override func prepareForReuse() {
super.prepareForReuse()
currentID = nil
thumbnailImageView.image = nil
}
}
이 패턴은 실무에서 가장 많이 사용됩니다.
방법 2. 모델 배열(items)를 항상 최신 상태에서 참조하도록 관리
예:
- diffable data source 사용
- snapshot 기반으로 indexPath와 모델을 매칭
- indexPath → identifier → 모델 구조로 설계
Diffable 데이터 소스 예시:
let item = dataSource.itemIdentifier(for: indexPath)
cell.configure(with: item)
여기서 identifier는 고유한 id이므로 indexPath가 바뀌어도 항상 안전합니다.
방법 3. indexPath로 직접 접근하지 말고 데이터 소스 계층을 사용
예:
let model = tableViewDataSource.model(at: indexPath)
cell.configure(with: model)
데이터 소스를 별도 클래스로 관리하면:
- indexPath 변화와 내부 모델 구조 변화가 분리됨
- UI는 “어떤 indexPath의 모델?”만 알면 됨
- 내부에서는 올바른 모델을 찾아주는 안정성을 보장
방법 4. reloadRows / performBatchUpdates 시 indexPath 신뢰하지 않기
다음처럼 직접 indexPath를 사용하는 패턴은 위험합니다.
self.items.remove(at: indexPath.row)
tableView.reloadRows(at: [indexPath], with: .automatic) // ❌ 이미 indexPath가 달라진 상태일 수 있음
안전한 방식:
self.items.remove(at: indexPath.row)
tableView.reloadData() // ✔️ 데이터 전체가 변경되었으면 전체 reload가 안전
또는 diffable data source로 변경.
5. 실무에서 자주 발생하는 실제 사례
5-1. Filtering 중 스크롤 시 데이터가 뒤죽박죽
- 검색한 순간 items 배열이 달라짐
- indexPath.row가 기존 배열과 대응되지 않아 잘못된 셀 정보 표시
5-2. 비동기 로딩 중 셀이 재사용되면서 이미지가 섞임
- indexPath 기반으로 로딩한 이미지가 잘못된 셀에 삽입됨
5-3. Sorting 후 reloadRows를 하여 잘못된 indexPath 업데이트
- 정렬 전에 계산된 indexPath를 정렬 후에 사용해 버그 발생
5-4. 여러 섹션에서 row 값만 보고 접근
items[indexPath.row] // ❌ 섹션이 다른데 row만으로 접근
6. 안전한 indexPath/모델 매칭 규칙
- indexPath.row를 모델의 “절대 위치”로 사용하지 않는다.
- 항상 “현재 데이터 소스 상태”를 기준으로 모델을 가져온다.
- 비동기 작업에서는 모델 식별자를 저장하고 일치 여부를 확인한다.
- reloadRows 대신 reloadData 또는 diffable snapshot을 사용한다.
- 셀 구성 시 indexPath가 아닌 model.id를 중심으로 동작하도록 설계한다.
7. 실무용 체크리스트
- cellForRowAt에서 indexPath로 직접 배열을 접근하고 있는가?
- filtering/sorting 후 indexPath를 다시 계산해 사용하고 있는가?
- 비동기 이미지 로딩 중 셀이 재사용될 때 매칭 검증을 하고 있는가?
- diffable data source를 도입할 수 있는 구조인가?
- section + row 구조에서 row만 사용하고 있지 않은가?
8. 요약
- indexPath.row는 “그 시점의 화면 위치”이지,
“이 셀이 영구적으로 가리키는 모델”이 아니다. - 데이터 소스가 바뀌면 indexPath는 언제든 달라질 수 있다.
- 모델과 셀을 안정적으로 매칭하려면:
- diffable data source
- model.id 기반 매칭
- prepareForReuse + ID 검증
등을 사용해야 한다.
핵심 문장:
indexPath는 변한다. 모델 ID는 변하지 않는다.
반응형

