DiffUtil을 왜쓰는가

devguri·2022년 9월 8일
0
post-thumbnail

DiffUtil

DiffUtil을 쓰는 이유

notifyDataSetChanged()의 성능문제

RecyclerView에서 업데이트 된 상태를 확인하기 위해 notifyDataSetChanged()를 사용하여 모든 리스트에 있는 데이터들을 확인한다. 이때 리스트에 1억개의 데이터들이 있고 1개의 데이터만 업데이트 됐다면 모든 리스트들 다시 만들어 렌더링하는 과정을 거쳐야한다 !

→ DiffUtil 클래스를 통해, 이전 데이터 상태와 현재 데이터 상태간의 차이 계산하여 변경된 데이터만 업데이트 한다.

DiffUtil

DiffUtil 이란 ?
리스트에 변경된 부분만을 감지하여 갱신시켜주는 것이다.

Nested classes

💡 중첩클래스란 다른 클래스 내부, 인터페이스 본문 내에서 선언이 발생하는 클래스이다.
  1. DiffUtill.Callback : 두 리스트 간의 차이를 계산하는 동안 DiffUtil에서 사용하는 콜백 클래스
  2. DiffUtill.DiffResult : DiffUtill.caluculateDiff(Callback, boolean)의 결과를 갖고 있는 클래스
  3. DiffUtill.ItemCallback : 리스트에서 null이 아닌 두 아이템 사이의 차이를 계산한 Callback클래스

Public methods

calculateDiff

calculateDiff(DiffUtil.Callback cb) : DiffUtil.Callback으로 받은 리스트 간의 차이를 통해 리스트의 업데이트 목록을 계산한다.

  • 인자로 DiffUtil.callback을 받아주고, 반환값으로 DiffUtil.DiffResult를 반환해준다.
  • DiffUtil.DiffResult : 오래된 리스트를 업데이트 된 리스트로 변환하는 정보를 포함하는 값이다.

  • 인자로 boolean detectMoves 를 받아준다 → DiffUtil이 이동된 아이템을 감지하려고 하면 True, 아니라면 false 반환
💡 DiffUtil 적용하는 코드와 함께 언급하겠음

DiffUtilCallback 관련한 클래스 생성한 전체 코드

class MyDIffUtilCallback(val oldList: List<UserData>, private val newList: List<UserData>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int = oldList.size

    override fun getNewListSize(): Int = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        return oldItem.name == newItem.name
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldList[oldItemPosition] == newList[newItemPosition]

}

DiffUtil.Callback

두 목록 간의 차이를 계산하는 동안 DiffUtil에서 사용하는 콜백 클래스

추상 메소드

  • areItemsTheSame(int oldItemPosition, int newItemPosition) : 두 아이템이 같은 객체를 가지고 있는지 여부를 반환한다.
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        return oldItem.name == newItem.name
    }
  • areContentsTheSame(int oldItemPosition, int newItemPosition) : 두 아이템이 같은 데이터를 가지고 있는지 여부를 반환한다.
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = 
				oldList[oldItemPosition] == newList[newItemPosition]

위에는 객체가 같아야하고, 이 함수는 해당 위치에 대한 데이터가 같아야한다.

  • getNewListSize() : 새로운 리스트의 사이즈를 반환
override fun getNewListSize(): Int = newList.size
  • getOldListSize() : 오래된 리스트의 사이즈를 반환
override fun getOldListSize(): Int = oldList.size

일반 메소드

  1. getChangePayload(int oldItemPosition, int newItemPosition) : areItemsTheSame(int, int)true 를 반환하고, areContentsTheSame(int, int)false를 반환하면 업데이트 내역이 있다고 판단한다. 그리고 DiffUtil은 이 메서드를 호출하여 변경 사항에 대한 페이로드를 가져옴
💡 이제 어뎁터에 DiffUtil을 적용시켜야함

DiffUtil.DiffResult

Public methods

  1. convertNewPositionToOld
public int convertNewPositionToOld (int newListPosition)

새로운 리스트에 대한 위치가 주어지면, 예전 리스트 위치를 리턴하거나 제거된 경우는 NO_POSITION 을 반환한다.

  1. convertOldPositionToNew
public int convertOldPositionToNew (int oldListPosition)

아전 리스트에 대한 위치가 주어지면, 새로운 리스트 위치를 리턴하거나 제거된 경우는 NO_POSITION 을 반환한다.

  1. dispatchUpdatesTo
public void dispatchUpdatesTo (ListUpdateCallback updateCallback)

업데이트 내역을 전달하는것으로 첫 번째 업데이트 호출은 이후에 오는 모든 업데이트 호출에 영향을 준다.

updateCallback 업데이트 작업을 보내기 위한 콜백을 인자값으로 전달

public void dispatchUpdatesTo (Adapter adapter)

지정된 어뎁터에게 업데이트 이벤트를 전달한다.
ex) 리스트에 의해 밀리는 어뎁터가 있다면, 새로운 어뎁터로 바꾼 후 이 메서드 호출하여 업데이트 내용을 RecyclerView로 보낸다.

fun updateList(){
        userList?.let{
            val diffCallback = MyDIffUtilCallback(this.userList, userList)
            val diffResult = DiffUtil.calculateDiff(diffCallback)

            this.userList.run {
                clear()
                addAll(userList)
                diffResult.dispatchUpdatesTo(this@UserAdapter)
            }
        }
    }
  • 새로운 데이터 넣게 되면, updateList() 호출하여 DiffUtil.calculateDiff 에 데이터 넣어서 업데이트 사항 확인
  • diffResult.dispatchUpdatesTo(this@UserAdapter) 를 통해 리사이티클러뷰 업데이트 시켜줌

DiffUtil.ItemCallback

Public methods

  1. areContentsTheSame(T oldItem, T newItem) : 두 아이템이 같은 데이터를 가지고 있는지(인자 : 객체 아이템을 전달)
  2. areItemsTheSame(T oldItem, T newItem) : 두 객체가 같은 객체를 표현하고 있는지
  3. getChangePayload(T oldItem, T newItem) : areItemsTheSame(T, T)true 를 반환하고, areContentsTheSame(T, T)는 false`를 반환하면 업데이트 내역이 있다고 판단한다. 그리고 DiffUtil은 이 메서드를 호출하여 변경 사항에 대한 페이로드를 가져옴

AsyncListDiffer

→ 리스트가 크면 시간이 많이 걸리기 때문에, 백그라운드 스레드에서 실행해야함

  • 백그라운드 스레드에서 DiffUtil을 통해 두 리스트간의 차이를 계산하는 것을 도와줌

백그라운드 스레드 가 무엇인가 ?
안드로이드 앱은 기본 스레드를 통해 UI 작업을 한다. 근데 이 기본 스레드에서 장기 실행 작업을 호출하면 작업이 정지될 수 있다. 그래서 UI 업데이트를 처리하는 동안 추가적으로 백그라운드 스레드를 만들어 장기 실행 작업을 처리할 수 있게 한다. (이름처럼 기본 스레드를 잠시 나둔채 뒤에서 작업하는 것)

  • LiveData 리스트로부터 값을 사용하고 어뎁터에 대한 데이터 보여준다.
    새로운 리스트들을 받으면 그 차이를 계산한다.
  • 이때 getCurrentList() 를 사용하여 현재 리스트에 접근하고 데이터 객체를 보여준다.
  • Diff 결과들은 현재 리스트 업데이트 전에 ListUpdateCallback에 보내진다.
  • 어뎁터에서 업데이트 된 리스트가 보인다면, getCurrentList()를 통해 리스트 아이템과 총 개수에 안전하게 접근했다는 것을 의미한다.
profile
Always live diligently

0개의 댓글