reusable table cell == cell recycling

프로토타입 셀을 하나 만들어 두고, 해당 프로토타입의 형식에 맞게 테이블 셀을 구성했는데 이 때 reusable cell을 사용했다.

데이터에 변화가 없었을 땐 괜찮았는데, 아래로 당겨서 새로고침을 하면서 데이터에 변화가 생기자 이 테이블 셀이 이상하게 작동하기 시작했다.

reusable인데… 그냥 덮어놓고 쓴 거죠…

문제 상황

bug

테이블 각 행에 텍스트가 있고, 해당 텍스트의 기간이 만료되면 텍스트가 회색 취소선이 있는 텍스트로 변하게끔 만들어 둔 상황.

그런데 뷰를 새로고침할 때마다 만료되지 않은 친구들까지 회색 취소선을 가진 텍스트로 바뀌었다. 그래서 아래와 같은 흐름에 따라 오류를 해결하려고 시도했다.

1. 데이터 확인

서버에서 데이터를 이상하게 줄 리 없다고 생각했지만, 혹시나 내 잘못이 아닐까…(ㅎ) 하는 마음으로 받아오는 데이터를 확인했다.

역시나 문제가 없었다.

2. tableView(_:cellForRowAt:) 검색

검색을 하려는데 이걸 어떻게 검색하나… 키워드도 모르겠고… 해서 일단 내가 해당 취소선 처리를 하는 함수를 검색했다.

tableView(_:cellForRowAt:) 친구는 테이블 뷰를 구성할 때 필수적으로 구현 해 주는 메서드이다. 인덱스 패스를 인자로 받아 테이블 뷰의 특정 위치에 셀을 위치시키는 기능을 한다.

각 셀을 구성할 때 그냥 무지성으로 처리해야 하는 데이터 개수만큼의 셀을 만드는 것이 아니라, dequeueReusableCell(withIdentifier:for:) 메서드를 사용해 셀을 재활용하라고 한다.

극단적인 예시로, 테이블 뷰를 구성하는 데이터가 1억 개인데, 한 번에 표시할 수 있는 총 셀의 개수는 10개라고 쳐 보자. 이 때, 1억 개의 셀을 한꺼번에 다 만드려면 많은 자원이 필요할 것이다. 한 번에 보여줄 수 있는 셀은 10개 뿐이니 그만큼(혹은 앞 뒤로 몇 개 더)만 만들어 놓고 남은 99999990개(혹은 그보다 몇 개 적은)의 셀은 필요할 때마다 만드는 것이다.

를 찬찬히 생각하다 보니…

3. 아

앞에서 만료됐다고 회색 취소선 표시된 셀을 재사용하는 다른 데이터에 적용됐구나.,, 어 그럼 뭔가 적용된 설정들을 삭제 해 주면 되겠다.

라고 해서 냅다 clear 이런 느낌의 메서드를 찾아서 적용 해 봤는데 제대로 안 됐다.

4. 해당 설정이 적용된 곳

그래서 다시 내가 구현한 tableView 메서드를 뜯어보다가 회색 취소선을 적용한 게 셀이 아니라 셀 안에 있는 라벨이라는 것을 인지했다.

라벨의 속성을 조건에 따라 지정해주기 직전에 라벨의 속성을 초기화하고 재실행했더니 위에서 맞닥뜨린 오류가 해결되었다!

bug fixed

더 알아낸 것

블로그 정리를 하려고 검색하던 중, reusable cell을 초기화할 수 있는 방법으로 prepareForReuse()를 재정의하면 된다는 정보를 접했다.

prepareForReuse()

이 친구는 UITableViewCell 객체가 reuse identifier를 가지고 있을 때, UITableView에서 dequeueReusableCell 메서드를 호출하기 전 실행하는 메서드라고 한다. 셀을 다시 쓸 수 있도록 준비하는 함수인데, 이 말은 셀에 지정된 속성들을 초기화한다는 뜻이다.

그냥 말만 봤을 땐, 앞서 라벨의 속성을 tableView 메서드에서 직접 초기화해준 것을 해당 함수에 오버라이드 하라는 말처럼 보인다.

그런데 애플 공식 문서에 따르면,

퍼포먼스에 이상이 생기는 것을 방지하기 위해, 알파값, 수정, 선택 상태 등과 같은 컨텐츠와 관련된 속성이 아닌, 오직 셀의 속성만 리셋해야 한다.
tableView(_:cellForRowAt:)항상 셀을 재사용할 때 모든 컨텐츠를 리셋해야 한다.

라고 되어 있다.

이 말은, 셀의 컨텐츠에 대한 속성은 tableView(_:cellForRowAt:) 메서드에서 초기화하고, 셀 자체에 대한 속성은 prepareForReuse() 메서드에서 초기화하는 것이다.

이와 같은 내용으로 스택 오버플로우 질문과 답변이 있어 두 메서드의 차이를 더 확실하게 알 수 있었다.

결론

재사용 셀을 사용할 때, 그 전 셀의 내용이 다음에 재사용되는 셀에 영향을 미칠 수 있으니, tableView(_:cellForRowAt:) 메서드에서 초기화시켜주자.

셀 자체의 초기화를 할 땐 prepareForReuse() 메서드를 오버라이드 해 주면 된다.

잘못된 정보나 더 나은 방향이 있다면 언제든 알려 주세요!

댓글남기기