RxSwift Race Condition on Cells
Posted on July 4, 2016
A few days ago I wrote a simple code to asynchronously load an image using RxMoya
. RxMoya
is an RxSwift
wrapper for Moya
. The flow is simple, everytime collection view asks for NSCollectionViewItem
(yes, I’m building a macOS app), I need to load the image asynchronously. Here’s a part of the implementation:
func collectionView(
collectionView: NSCollectionView,
itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath
) -> NSCollectionViewItem {
let item = collectionView.makeItemWithIdentifier(
"ChapterItem",
forIndexPath: indexPath
) as! ChapterItem
let chapter = vm[indexPath.item]
let chapterPageVm = ChapterPagesViewModel(..)
// Fetch manga chapter pages
chapterPageVm.fetch()
chapterPageVm
.chapterPages
.drive(onNext: { _ in
// After chapter pages are loaded
// we will set chapter preview here
})
.addDisposableTo(disposeBag)
...
return item
}
Obviously there’s a problem with the implementation, how do I make sure that after the chapter pages are loaded, the execution context of driveNext
is valid? In other words, how to make sure that the cell item has not been re-used (because if it is and I’m setting the fetched data, then there will be race condition)?
A solution that might work is by setting a DisposeBag
to each cell and disposing it everytime didEndDisplayingItem
gets called.
Sounds like a perfect plan! Now I just need to find a way to set the dispose bags and keep track of them for each cell. Luckily RxSwift
has a great community around it, dpaschich
on rxswift.slack.com suggested to set DisposeBag
on each of the cell item and disposing it using a custom method didEndDisplaying
class ChapterItem: NSCollectionViewItem {
@IBOutlet weak var chapterTitle: NSTextField!
@IBOutlet weak var chapterNumber: NSTextField!
@IBOutlet weak var chapterPreview: NSImageView!
var disposeBag = DisposeBag()
func didEndDisplaying() {
chapterPreview.image = .None
disposeBag = DisposeBag()
}
}
func collectionView(
collectionView: NSCollectionView,
didEndDisplayingItem item: NSCollectionViewItem,
forRepresentedObjectAtIndexPath indexPath: NSIndexPath
) {
let _item = item as! ChapterItem
_item.didEndDisplaying()
}
func collectionView(
collectionView: NSCollectionView,
itemForRepresentedObjectAtIndexPath indexPath: NSIndexPath
) -> NSCollectionViewItem {
let item = collectionView.makeItemWithIdentifier(
"ChapterItem",
forIndexPath: indexPath
) as! ChapterItem
let chapter = vm[indexPath.item]
let chapterPageVm = ChapterPagesViewModel(..)
// Fetch manga chapter pages
chapterPageVm.fetch()
// Notice how I add disposable to item.disposeBag
chapterPageVm
.chapterPages
.driveNext { _ in
// After chapter pages are loaded
// we will set chapter preview here
} >>> item.disposeBag
...
return item
}