[iOS] - Diff 사용해서 UICollectionView data reload하기


Ordered collection diffing을 사용해서 UICollectionView의 데이터를 효율적으로 reload하자.


Diff

Diff란?

한 collection에서 다른 collection으로의 변환을 도와주는 기능

  • 기존의 문제점 : 상태를 표현하고, 구성하고, 두 상태 간의 transaction을 적용하는 코드는 복잡하고, 에러가 발생할 확률이 높다. ➡️ diff를 이용해서 collection 타입에 변화를 적용하기 쉬워졌다.
  • iOS -> iOS 13.0, swift 5.1 ~

Diff

image

“BEAR” -> “BIRD”

image

  • “E”, “A” remove
  • “I”, “D” insert

기존(예시)

  1. “BEAR”, “BIRD” 비교
  2. “BEAR”에서 remove 해야 하는 부분 찾아서 index를 이용해 삭제
  3. “BIRD”가 되기 위해 insert 해야 하는 부분 찾아서 올바른 index에 추가

👎 지금은 간단한 문자열, 데이터가 복잡하지 않아서 쉬운 작업일 수 있으나, 데이터가 복잡한 경우 이런 작업들이 쉽지 않음. index를 일일이 찾아서 remove, insert를 하는 작업도 귀찮고, 올바르지 않은 index를 대입하게 될 가능성 있음

diff를 사용한다면?

ios image

  1. 두 collection(여기서는 글자의 collection인 문자열)의 차이 받기
  2. 차이(변화) 적용하기

👍어떤 index의 어떤 element에 어떤 operation을 해야 하는지 직접 계산할 필요 없음, 코드도 간단해짐

여기에서 diff를 출력하면 아래와 같이 나오는데, 즉 필요한 최소한의 연산들을 수행해야 하는 인덱스와 함께 보여준다.

image

적용할 수 있는 element의 조건?

  • swift github image image image
  • Equatable을 따르는 타입의 element에 적용가능
  • image 객체간 동일성 체크를 해야 하는데, 객체가 복잡할 경우 Equatable 함수를 실행하는 데 시간이 오래 걸릴 수 있음. Equatable을 따르는 Hashable을 element가 따르게 해서 hash value로 비교하게 해서 실행 시간을 줄일 수 있다. 출처

diff의 원리?

알고리즘까지 설명하기에는 글이 너무 길어져서 이 글을 통해 따로 작성했다. 추가로 diff에 대한 간단한 사용법, 설명을 보려면 이 글을 참고하면 된다.

어디에 사용할까?

CollectionView와 함께 사용하면 유용하다. CollectionView에 데이터를 출력하기 위해 백엔드에서 데이터를 불러오고, 데이터에 변화가 생겼을 때 이 변화(특정 아이템이 추가/삭제됨)를 인터페이스에 반영하기 위해 인터페이스를 업데이트한다.

iOS의 경우, 여기에서 전체 리스트를 새로운 데이터로 새로고침하는 reloadData를 사용하기 쉽다. 하지만 reloadData를 사용해서 전체 데이터를 새로고침하면 아래와 같은 문제점이 있다.

  1. UITableView의 경우 cell size를 다시 계산해야 하므로 성능을 저하시킨다.
  2. 모든 cell, header, footer에 변화가 없다 해도 reload를 하므로 비효율적이다.
  3. reloadData는 세부적인 컨트롤이나 일어나는 변화를 애니메이션으로 보여주지 않는다.

따라서 변화가 일어나는 것을 사용자에게 보여주기 위해서 직접 CollectionView에 행을 추가하고 삭제하는 편이 선호된다.

CollectionView

아래와 같이 여러 사진이 있고, 하단의 reload data 버튼을 누르면 랜덤으로 출력해야 하는 이미지들의 개수, 순서가 바뀌는 화면이 있다. 전체 프로젝트의 코드를 보려면 여기를 참고하면 된다.

image

데이터가 적고 간단한 경우

  • 최대 데이터 개수가 3개임을 가정

reloadData로 데이터를 reload할 때

  • 코드 image
  • 실행화면
  • simpleReload

  • 애니메이션이 보여지지 않음
  • 비효율적인 방법(그나마 데이터가 적어서 괜찮음)

직접 로직을 짜서 데이터를 reload 할 때

  • 데이터 전체를 새로운 데이터로 Reload하는 작업은 비효율적이므로 모든 데이터를 reload하지 않고 특정 item을 삭제, 추가해서 Reload할 경우
  • 코드 image
  • 원래 데이터에서 삭제 되어야 하는 item, 변할 데이터에서 새로 추가되는 item들을 모두 계산하고 이 새로운 item들을 추가해야 하는 index를 계산하기도 쉽지 않음

❗️또 다른 문제는 데이터가 많고 데이터가 변하는 과정이 복잡해질 때 발생

데이터가 많고 변하는 과정이 복잡한 경우

reloadData로 데이터를 reload할 때

  • 실행화면 complexReload

  • 빨라서 잘 보이지 않지만, 모든 이미지가 완전히 다시 로딩되는 것을 볼 수 있음. cell을 재활용하는데 이미지를 다시 다운 받을 필요는 없는데 이런 작업이 수행되고 있음을 확인할 수 있음
  • 지금도 데이터의 개수가 충분히 많지 않아 빠른 시간 내 reload가 수행이 되지만, 한 cell을 출력하기 위해 오래 다운로드를 수행하는 작업이 있을 경우 한 번 reload 해서 모든 cell을 보기까지 굉장히 오래 걸릴 것임.

diff를 사용해서 데이터를 reload 할 때

  • 코드
  • image
  • diff가 삭제되어야 하는 item들, 추가되어야 하는 item들이 어떤 index에서 연산이 일어나야 하는지까지 모두 계산해서 알려줌
  • 따라서 UI를 업데이트 해야 하는 부분에서 삭제되어야 하는 item들은 삭제하고, 추가되어야 하는 item들을 추가하는 작업을 수행하는 코드를 작성하면 끝이다.
  • 실행화면 complexReloadDiff
  • 재사용되는 cell들을 제외하고 새롭게 추가되는 item들에만 이미지가 다운로드 되는 것을 확인할 수 있다.
  • 애니메이션도 잘 보여진다.

결론

  • CollectionView에서 데이터를 reload 할 때, diff를 사용하면 효율적으로 데이터를 Reload할 수 있다.
  • 데이터가 많고, 데이터가 변하는 과정이 복잡하다면 diff를 사용하는 것이 효율적이다.