Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dwifft & ReactiveCocoa

I love Dwifft but I would love even more to use it with ReactiveCocoa to help reduce code complexity in my collection view controllers even more.

Currently, I have a helper class that takes an instance of SignalProducer<[[T]]> where T: Equatable (so it works with the differ). Every time the signal producer emits a new value:

    self.data.producer.observeOn(UIScheduler()).on(next: { [unowned self] in
        guard let collectionView = self.collectionView else { return }
        for (index, element) in $0.enumerate() {
            if index == self.diffCalculators.count {
                let calculator = CollectionViewDiffCalculator<T>(collectionView: collectionView, initialRows: element)
                calculator.sectionIndex = index
                self.diffCalculators.append(calculator)
            } else {
                let calculator = self.diffCalculators[index]
                calculator.rows = element
            }
            for index in self.data.value.count..<(self.diffCalculators.count >= self.data.value.count ? self.diffCalculators.count : self.data.value.count) {
                self.diffCalculators.removeAtIndex(index)
            }
        }
    })
        .takeUntil(self.willDeallocSignal())
        .start()

Here, while enumerating through my 2d array, if a diff calculator doesn't exist yet, one is created and added to my storage array, diffCalculators. If one does exist, the rows property is set. Afterwards, I loop through the remaining sections and remove them.

Unfortunately, I've been incredibly unsuccessful in getting this to work. Time after time I get a The number of sections contained in the collection view after the update (1) must be equal to the number of sections contained in the collection view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).' and I can't tell if that's in my logic or if I'm using Dwifft wrong.

Any suggestions?

Bounty edit:

For reference, here's the helper class I've built to bind together collection views with reactive cocoa data: https://gist.github.com/startupthekid/b3a69363d83e2279da0d750959c5a930

What I need is a way to produce and modify CollectionViewDiffCalculators in a reactive, thread safe manner. Currently using side-effects crashes depending on how quickly I receive new data (one diff is getting calculated, data comes in, and the collection view attempts a reload at the same time).

like image 533
barndog Avatar asked Nov 09 '22 15:11

barndog


1 Answers

I discovered after much trial and tribulation that the issue wasn't actually on my side, it was inside the internals of Dwifft. Dwifft makes the assumption the rows will be set once and from there on out, inserted into or removed from.

But when you bind an array with ReactiveCocoa, the entire array is being overwritten every time the signal producer emits so what ends up happening is matching insertions and deletions will be generated for a single emission. Items will then attempt to be inserted and deleted and that's where the crashes come from.

The way to get around that is:

self.diffs <~ self.data.producer.combinePrevious([])
        .map { Zip2Sequence($0.0, $0.1) }
        .map { $0.map { $0.0.diff($0.1) } }
        .on(next: { [weak self] in
            let changes = $0.enumerate().map { index, diff in (diff.insertions.map({ NSIndexPath(forItem: $0.index, inSection: index) }), diff.deletions.map({ NSIndexPath(forItem: $0.index, inSection: index) })) }
            let insertions = changes.map { $0.0 }.flatMap { $0 }
            let deletions = changes.map { $0.1 }.flatMap { $0 }
            if !Set(insertions).subtract(deletions).isEmpty {
                self?.collectionView?.performBatchUpdates({
                    if !insertions.isEmpty { self?.collectionView?.insertItemsAtIndexPaths(insertions) }
                    if !deletions.isEmpty { self?.collectionView?.deleteItemsAtIndexPaths(deletions) }
                }, completion: nil)
            }
        })

Essentially the same as the internals of Dwifft but with the added checks of if !Set(insertions).subtract(deletions).isEmpty { ... }.

like image 149
barndog Avatar answered Nov 15 '22 04:11

barndog