RxSwift를 통해 TableView를 구성하고, 그 TableView Cell안에 있는 버튼을 탭했을때의 동작을 구현하고 있었는데,
다음과 같은 문제가 발생했다.

TableView를 스크롤을 한 이후에 '받기'라는 Cell안의 버튼을 누르면 push가 계속 되는 현상이 발생했다...!
이번 글에서는 이러한 현상이 발생하는 이유와 해결법에 대해 알아보겠다.
Cell의 중첩 구독 문제
코드는 다음과 같다.
items
.bind(to: tableView.rx.items(cellIdentifier: SearchTableViewCell.identifier, cellType: SearchTableViewCell.self)) { (row, element, cell) in
cell.appNameLabel.text = element
cell.appIconImageView.backgroundColor = .lightGray
// 받기 버튼을 눌렀을 때 bind
cell.downloadButton.rx.tap
.bind(with: self) { owner, _ in
print("다운로드 버튼 클릭")
// DetailView로 이동
owner.navigationController?.pushViewController(DetailViewController(), animated: true)
}
// owner는 위의 클로저 내에서만 쓸 수 있기 때문에 일단 self로 씀
.disposed(by: self.disposeBag)
}
.disposed(by: disposeBag)
items라는 배열을 바인딩해서 tableView에 표시하는 동작이다.
그리고 그 tableView안에 downloadButton이라는 버튼이 있고,
그 버튼을 탭하면 DetailView로 이동하는 형태이다.
그런데 문제는 버튼을 구독할 때 disposeBag이 ViewController에서 생성한 DisposeBag()의 인스턴스에 의존하고 있다!!
그리고 TableViewCell은 재사용된다는 특징이 있다. 재사용 매커니즘에 대한 내용은 아래 글을 참고하면 된다.
https://dev-voo.tistory.com/25
[iOS/UIKit] awakeFromNib과 prepareForReuse - 셀의 재사용 원리
TableView나 CollectionView에서 CustomCell을 XIB로 생성하게 되면 awakeFromNib()이라는 메서드가 생기게 된다.쉽게 이 함수의 역할을 설명하자면 ViewController에서 viewDidLoad의 역할과 비슷하다고 생각하면 된
dev-voo.tistory.com
이러한 특징을 미루어봤을 때, 문제의 원인은 다음과 같다.
- 처음 TableView가 보여질때 재사용될 Cell들이 ViewController의 DisposeBag에 등록된 상태
- 스크롤을 하면서 위에 있던 Cell이 재사용되면서 이미 DisposeBag에 등록된 Cell들이 다시 DisposeBag에 추가가 됨
- 즉, DisposeBag에 같은 Cell이 중첩돼서 추가가 되면서 Cell의 중첩 구독현상이 발생!
그래서 받기 버튼을 눌렀을때 여러번 push가 되는 현상이 발생했던 것이다.
Cell 중첩 구독 문제 해결법
tableViewCell Class 내의 prepareForReuse에서 별도의 DisposeBag의 인스턴스를 생성해서,
ViewController의 DisposeBag에 의존하지 않는 형태로 만들어주면 된다.
// TableViewCell
final class SearchTableViewCell: UITableViewCell {
var disposeBag = DisposeBag()
//...
override func prepareForReuse() {
super.prepareForReuse()
// 재사용 될 때 disposeBag에 새로운 인스턴스를 갈아끼움으로써 구독을 해제
disposeBag = DisposeBag()
}
//...
}
이후에 ViewContoller에서는 다음과 같이 ViewController의 DisposeBag이 아닌, 각 cell의 DisposeBag에 의존하는 형태로 만들어준다.
items
.bind(to: tableView.rx.items(cellIdentifier: SearchTableViewCell.identifier, cellType: SearchTableViewCell.self)) { (row, element, cell) in
cell.appNameLabel.text = element
cell.appIconImageView.backgroundColor = .lightGray
cell.downloadButton.rx.tap
.bind(with: self) { owner, _ in
print("다운로드 버튼 클릭")
owner.navigationController?.pushViewController(DetailViewController(), animated: true)
}
// VC가 아닌 각 cell의 disposeBag에 의존하도록 함
.disposed(by: cell.disposeBag)
}
.disposed(by: disposeBag)
위의 글에서 prepareForReuse에 관한 내용을 참고해보면, 이제 Cell이 재사용될 때마가 새로운 DisposeBag 인스턴스를 생성하므로써 같은 DisposeBag에 중첩돼서 추가될 일이 없어진다.
이제 Cell이 재사용될때 새로운 DisposeBag 인스턴스로 갈아끼워지기 때문에 구독이 해제되면서 중첩구독 문제가 해결된다!
'iOS > UIKit' 카테고리의 다른 글
| [iOS/UIKit] Diffable DataSource를 이용해 서로 다른 Cell로 구성하기 - Xcode16에서 발생할 수 있는 오류 (0) | 2025.02.28 |
|---|---|
| [iOS/UIKit] debounce VS throttle (0) | 2025.02.26 |
| [iOS/UIKit] RxSwift 용어정리 (0) | 2025.02.20 |
| [iOS/UIKit] View Hierarchy 디버깅 팁 (1) | 2025.02.02 |
| [iOS/UIKit] 프로젝트 트러블슈팅 (0) | 2025.01.28 |