-Today's Learning Content-

  • PrepareForReuse

1. PrepareForReuse에 대해

내용 정리

프로젝트를 진행하던 중 UI 버그가 일어나서 해결하다가 PrepareForReuse에 대해 다시 공부해보다...

1) UI 버그 발견

프로젝트를 진행하던 중 UI에 버그가 발견되었다.

셀의 하단에 요일이 중복되어 표시되는 버그가 발생한 것이다...
이 버그는 처음부터 발생하지는 않고, 셀을 스크롤해서 재사용 되거나 새로운 셀을 추가하면 발생하는 버그였다.

왜 이런 버그가 발생하는지 원인을 찾아 브레이크 포인트를 여기저기 걸고 다녔는데,
발견한 원인은 PrepareForReuse에 있었다.

// 셀 재사용 옵션
override func prepareForReuse() {
	super.prepareForReuse()
        
	setupUI()
}

기존에 내가 작성한 코드는 위와 같은 형식이었는데, 저 setupUI() 메소드가 문제였다.
setupUI() 메소드는 말 그대로 셀의 UI를 모두 설정하는 메소드인데, 저 메소드 안에서는 addSubView(_:) 메소드와 오토레이아웃을 설정하는 메소드도 포함이 되어있다.
때문에, 셀이 재사용 될 때마다 새로운 뷰를 추가하고 오토레이아웃을 설정하고... 이런 과정이 반복되는 탓에 위의 사진처럼 UI가 중복되어 표시되거나 중첩되어 보이는 버그가 발생한 것이다.

원인을 파악한 후에는 아래와 같이 코드를 바꾸어 주었다.

// 셀 재사용 옵션
override func prepareForReuse() {
	super.prepareForReuse()
        
	prepareForReuseData()
}

func prepareForReuseData() {
	self.weekdaysStatus.accept([])
	self.timeLabel.text = ""
	self.note.text = ""
}

PrepareForReuse만을 위한 메소드를 하나 만들고, 메소드에서는 셀의 UI에 필요한 데이터를 초기화 시키도록 구현했다.
이렇게 재사용 옵션을 수정하니 아래와 같이 UI 버그가 사라졌다.

그래서 내가 지금까지 PrepareForReuse에 대해 오해를 하고 있었던 것 같아 이번 기회에 다시 공부를 해보았다.

2) PrepareForReuse란?

PrepareForReuse는 UIKit의 UITableViewCellUICollectionViewCell에서 사용되는 메소드로, 재사용을 위해 셀이 초기화되는 시점에 호출된다.
재사용 큐를 사용하는 테이블뷰와 컬렉션뷰의 성능을 최적화하는 데 필수적인 역할이다.

3) PrepareForReuse를 사용하는 이유

테이블뷰나 컬렉션뷰의 셀은 스크롤하면서 계속 생성되는 것이 아니라 재사용(reuse)되기 때문에, 이전 데이터나 상태를 초기화하지 않으면 셀이 이상하게 보이거나 잘못된 데이터를 보여주는 등 버그가 발생할 수 있다.

또, 이미지 다운로드 같은 비동기 작업이 완료되기 전에 셀이 재사용되면, 잘못된 이미지를 표시할 가능성이 있다.

때문에 PrepareForReuse를 통해 이전 셀의 데이터나 상태를 초기화하여 셀의 UI 구성 요소를 새 데이터로 업데이트하기 전에 리셋하는 것이다.

4) 반드시 초기화 해야할 항목들

PrepareForReuse를 사용하여 반드시 초기화를 해주는 것이 좋은 것들은 아래 3가지가 있다.

  1. UI 데이터 초기화
override func prepareForReuse() {
    super.prepareForReuse()
    myLabel.text = nil
    myImageView.image = nil
}
  1. 상태 초기화
    스위치, 체크박스 등의 상태를 리셋한다.
override func prepareForReuse() {
    super.prepareForReuse()
    mySwitch.isOn = false
    myCheckbox.isChecked = false
}
  1. 비동기 작업 취소
    셀에서 시작된 네트워크 요청은 셀이 재사용되기 전에 취소해야 한다.
override func prepareForReuse() {
    super.prepareForReuse()
    imageDownloadTask?.cancel()
    imageDownloadTask = nil
}

위의 3가지 작업은 PrepareForReuse를 사용하여 반드시 초기화를 해주는 것이 좋다. 그렇지 않으면 위에서 언급한 버그들이 발생할 수 있기 때문이다.

5) DisposeBag 재초기화

RxSwift에서 DisposeBag을 초기화해서 불필요한 바인딩을 방지할 수 있다.

override func prepareForReuse() {
    super.prepareForReuse()
    disposeBag = DisposeBag() // Re-initialize
}

셀 재사용시 disposeBag을 다시 초기화 시켜주는 것인데, 이는 셀의 재사용 시 불필요한 Rx 바인딩을 해제하기 위해 꼭 필요한 패턴이다.

RxSwift를 사용하면 컬렉션뷰나 테이블뷰의 데이터소스를 바인딩할 수도 있고, 델리게이트를 사용하지 않고도 셀을 선택하거나 특정 동작에 대한 것들을 바인딩할 수 있다.

그런데, 셀은 재사용되기 때문에 이전의 Rx 구독이 남아 있다면 새 데이터와 엉킨 상태로 잘못된 동작을 할 가능성이 높다.
때문에 셀을 재사용할 때 disposeBag을 새로 초기화하여 기존 구독을 모두 해제한 후 새로운 구독만 관리할 수 있도록 하는 것이다.

이를 통해 메모리 누수와 중복 구독을 방지할 수 있다.

6) 결론

이런 간단한 이유 때문에 UI가 버그가 발생하다니... 정말 가볍게 공부할 내용은 하나도 없는 것 같다.
그래도 이런 버그 덕분에 또 공부를 하고, 알아가는 것이 생기니까 좋다고 생각한다.

그치만...
아직도 처리해야할 UI 버그는 많다...

이런 UI 겹침 문제라던지...

남은 프로젝트도 파이팅...ㅎ


-Today's Lesson Review-

RxSwift가 문제야
profile
이유있는 코드를 쓰자!!

0개의 댓글