반응형

 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는 해당 셀이 그 순간 어떤 위치에 있는지를 나타낼 뿐,

그 셀과 연결된 모델을 “영구적/고정적으로” 보장하지 않습니다.

즉, 다음과 같은 상황이 발생할 수 있습니다.

  1. 셀 생성 시 indexPath.row == 3
  2. 데이터가 로딩되면서 items 배열이 변경
  3. 셀이 재사용되며 configure가 다시 호출되기 전에 indexPath가 달라짐
  4. index 3의 데이터는 완전히 다른 아이템이 됨
  5. 셀에는 “예전 데이터”가 잘못 표시되거나 섞여 보임

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/모델 매칭 규칙

  1. indexPath.row를 모델의 “절대 위치”로 사용하지 않는다.
  2. 항상 “현재 데이터 소스 상태”를 기준으로 모델을 가져온다.
  3. 비동기 작업에서는 모델 식별자를 저장하고 일치 여부를 확인한다.
  4. reloadRows 대신 reloadData 또는 diffable snapshot을 사용한다.
  5. 셀 구성 시 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는 변하지 않는다.

반응형
Posted by 까칠코더
,