iOS 개발할 때 사용하게 되는 UI Cell.
iOS 에서 이 Cell 들을 사용할때는, 보여줘야할 Cell 들을 모두 새롭게 생성하는 것이 아니라 화면에 보이는 부분만 생성하고 재활용하는 로직을 수행한다. 메모리 효율성을 고려한 것.
이 때 일반적으로, 셀을 재활용하기전에 초기화해야하는 세팅들을 prepareForReuse() 라는 메서드 안에 코드 작성한다.
RxSwift 와 UITableViewCell / UICollectionViewCell 을 함께 사용할 때 유의하면 좋을 2가지.
각 Cell 에 대한 구독(subscribe)을 했을 경우, prepareForReuse 안에 구독 해제(dispose) 코드를 반드시 작성한다.
cell 내부에 disposeBag 이 있다면, cell 의 disposeBag 을 사용한다.
// TableViewCell
class SomeTableViewCell: UITableViewCell {
var disposeBag = DisposeBag()
let button = UIButton()
...
override func prepareForReuse() {
super.prepareForReuse()
// prepareForReuse 에서 명시적 구독 해제를 해야한다.
disposeBag = DiposeBag()
}
}
// 셀 외부에서 셀 내부 이벤트 스트림을 구독할 수 있다.
extension Reactive where Base: SomeTableViewCell {
var buttonTapped: ControlEvent<Void> { base.button.rx.tap }
}
SomeTableViewCell 이라는 Cell 내부에서 스트림이 발생하고, 이 스트림을 외부에서 구독할 경우가 있을 수 있다.
이 경우 prepareForReuse() 에서 명시적 구독 해제를 해주지 않으면 재활용하게되는 다른 셀에서 재활용된 구독을 하기 때문에 원하지 않는 엉뚱한 값을 받게 된다.
button 의 title 값을 prepareForReuse 에서 초기화 해주지 않았을 경우 다른 셀에서 의도되지 않은 title 값을 받을 수 있게 되는 현상과 같은 논리.
class SomeViewController: UIViewController {
// ...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(SomeTableViewCell.self, for: indexPath)
guard let item = dataSource[safe: indexPath.row] else { return cell }
// 위에서 선언했던 셀 내부 스트림을 구독해서 사용한다.
cell.rx.buttonTapped
.subscribe {
// ... 구독해서 사용할 뭔가의 행동
}
// 셀에 속한 스트림이기 때문에 셀의 생명주기에 맞게 구독해제한다.
.disposed(by: cell.disposeBag)
}
RxSwift 를 사용하다보면, disposed(by: disposeBag)
을 무의식에 습관처럼 쓰게 된다.
cell 의 스트림인 경우에는 disposeBag 이 아닌 cell.disposeBag 에 처리해줄 수 있도록 유의하는 것이 좋다.