Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift asDriver map and strong self

Tags:

swift

rx-swift

I'm trying to figure out if I'm creating a retain cycle here. I want to bind the current offset of the collection view to a UIPageControl with the following:

collectionView
    .rx
    .contentOffset
    .asDriver()
    .map { Int($0.x) / Int(self.collectionView.frame.width) }
    .drive(pageControl.rx.currentPage)
    .disposed(by: disposeBag)

Just wondering if this is ok, or if that self will create a retain cycle?

like image 483
Mr.P Avatar asked Oct 24 '25 18:10

Mr.P


1 Answers

Yes, your code creates a retain cycle because your chain keeps a strong reference to self and self keeps a strong reference to the chain (through the disposeBag.)

Also, such code is a code smell because you are depending on side effects in a map (the value of self.collectionView.frame.width changes over time) which you shouldn't do because it makes your code untestable.

Here is a more architecturally correct solution:

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var pageControl: UIPageControl!

    private let collectionViewFrame = ReplaySubject<CGRect>.create(bufferSize: 1)
    private let disposeBag = DisposeBag()

    deinit {
        collectionViewFrame.onCompleted()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        currentPage(
            offset: collectionView.rx.contentOffset.asObservable(),
            frame: collectionViewFrame.asObservable()
        )
            .bind(to: pageControl.rx.currentPage)
            .disposed(by: disposeBag)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionViewFrame.onNext(self.collectionView.frame)
    }
}

// This is your logic. Since it is separate from your view code, it can be tested/reused.
func currentPage(offset: Observable<CGPoint>, frame: Observable<CGRect>) -> Observable<Int> {
    return Observable.combineLatest(offset, frame)
        .map { Int($0.0.x) / Int($0.1.width) }
}
like image 140
Daniel T. Avatar answered Oct 26 '25 07:10

Daniel T.