UICollectionView 셀 재사용 문제: 빠르게 스크롤시 잘못된 이미지가 나타나는 현상

jane·2022년 6월 10일
2

iOS

목록 보기
24/32
post-thumbnail

이전 글에서는 이미지 로딩 속도를 개선하는 방법에 대해 알아보았다.
읽고 오시면 이번 글 이해에 도움이 됩니다!😆

빠르게 스크롤시 잘못된 이미지가 나타나는 현상

UICollectionView나 UITableView를 만들때 셀에 이미지가 포함된 경우 빠르게 스크롤시 잘못된 이미지가 들어가는 상황이 발생한다.

문제의 원인

이런 상황이 발생하는 이유는

- 하나의 셀이 계속 재사용되면서 여러 정보를 표현함

iOS에서는 화면에 최대로 보여지는 셀 개수로만 컬렉션뷰를 구성하고, 스크롤하는 순간 화면에서 사라진 셀을 재사용하여 화면에 새로 보여지는 셀을 만든다.

이렇게 셀을 재사용하면 매번 새로운 셀을 생성할 필요가 없기때문에 성능상의 이점을 가질 수 있지만, 반대로 한 셀에 여러 정보들이 지워졌다가 담겼다가 하는 과정에서 잘못된 정보가 나타날 수 있는 문제가 생긴다.

- 비동기적으로 작동하는 이미지 로딩 작업

이미지 로딩 작업은 무거운 작업이기 때문에 메인큐가 아닌 글로벌 큐에서 작업한다. 또한 비동기적으로 작동하기 때문에 끝나는 순서를 보장하지 못한다.

셀 재사용 + 비동기 작업으로 나타난 헬파티임

그러니깐 셀이 재사용되면서 새로운 이미지를 받아와야하는데

  • 새로운 이미지가 만약 늦게 로딩되어서 그 전까지는 기존 이미지가 보여지는 문제가 있을 수도 있고
  • 더 최악의 경우에는 새로운 이미지가 로딩이 되었는데 그 뒤에 기존 이미지가 로딩이 되어서 아예 잘못된 이미지가 보여질 수도 있는 것이다.

이미지 로딩 작업은 비동기라서 아무도 그 순서를 예측할 수 없기 때문에,,, ㅎ

셀이 재사용되는 과정을 살펴보면

  1. 화면에서 셀이 벗어나자마자
  2. prepareForReuse에서 초기화된 후
  3. 재사용 큐로 들어가고
  4. 재사용 큐에서 dequeue되어 다시 사용된다.

이 순서가 맞는지 궁금해서 직접 실험해봤는데
각 셀에 UUID를 주고 UICollectionViewCell의 prepareForReuse 메서드와 UICollectionView DataSource의 cellForItemAt 메서드를 각각 프린트해보았다.

아래 그림에서 6개의 셀이 처음에 dequeue되고 나서 재사용되는 모습을 볼 수 있다.
흥미로운 점은 재사용 큐에 들어간 순서대로 dequeue될줄 알았는데 아니라는 점,,,

prepareForReuse

그렇다면 지금까지 말한 prepareForReuse는 대체 뭐냐

컬렉션뷰의 prepareForReuse 문서에 가보면
prepareForReuse는 컬렉션뷰가 셀을 디큐하기 전에 불리는 메서드로
프로퍼티를 기본값으로 설정하거나 뷰를 재사용할 수 있는 상태로 만들때 override해서 사용하라고 한다.
근데 새로운 데이터를 할당하지는 말라고 한다(그건 dataSource의 역할이라)

그렇다는 것은.. 여기서 이미지뷰를 초기화해도 된다는것...?

근데 헷갈리는건 테이블뷰의 prepareForReuse 문서에 가보면
prepareForReuse에서는 성능상의 이슈를 피하기 위해서 셀의 컨텐츠와 무관한 속성만 리셋하라고 한다.

그렇다는 것은.. 여기서 이미지뷰를 초기화하면 안된다는것...?
컬렉션뷰랑 말이 다른데..? 검색을 해보아도 이건 의견이 분분하다...
이 부분은 아직 잘 모르겠네,,

해결방법

두가지 방법으로 해결할 수 있다.
1. 네트워크 통신 작업 전에 이미지를 초기화하기
2. 셀이 다시 사용되기 전에 불리는 prepareForReuse에서 진행중인 네트워크 요청 취소하기

이전 포스팅에서 만들었던 UIImageView의 서브클래스인 DownloadableImageView이다.

여기서 두가지 부분을 추가해주었는데

  1. 네트워크 통신을 통해 이미지를 로딩하기 전에
    self.image = UIImage() 를 넣어주어 초기화하기
    이미 캐싱된 이미지를 가져오는 경우에는 비동기 작업이 없기때문에 잘못된 이미지가 보여질 가능성이 없다.
    따라서 캐싱된 이미지가 없을때 네트워크 통신을 하게 되는데 그 전에 셀의 이미지를 초기화하여 셀이 재사용되기전의 기존 이미지가 남아있다면 초기화해준다.

  2. dataTask를 취소하는 cancelLoadingImage 메서드 생성
    셀이 재사용되려고 큐에 들어갔다가 나온 시점에서도 만약 셀이 재사용되기 전의 이미지를 받아오는 작업이 끝나지 않았다면 그 작업을 취소한다.

아래 사진에서 breakpoint로 새로 추가한 두 부분을 표시해뒀다.

그리고 커스텀 컬렉션뷰 셀에서 prepareForReuse를 override하여
아까 만든 cancleLoadingImage를 실행한다.

이렇게 하면 셀이 재사용되기 전에 아직도 진행중인 네트워크 통신이 있다면 취소되고, 기존 이미지가 남아있는 경우 새로 받아오는 네트워크 통신 작업 전에 초기화해줌으로써 문제점들을 해결할 수 있다~!

더 알아보고 싶은 부분

해결 방안을 검색해보다가 찾은...
셀의 indexPath나 이미지의 URL를 비교해서 셀에 알맞은 데이터가 들어가는지 확인하는 방법도 있었는데
일단 위에서 소개한 두 방법으로 해결이 되어서 이건 다음에 알아보겠다. ㅎㅎ
indexPath 구하는 방법

Reference

UITableViewCell shows the wrong image while images load

profile
제가 나중에 다시 보려고 기록합니다 ✏️

0개의 댓글