I'm working with RxSwift and have a scenario where I use a BehaviorRelay in my ViewModel to hold an array of data for a UITableView. I've set up a subscription to this BehaviorRelay specifically to trigger reloadData on my UITableView. However, I'm wondering about the best practice for accessing the array data in my UITableViewDataSource methods.
Here's a simplified version of what I'm doing:
class ViewModel {
let dataRelay = BehaviorRelay<[Section]>(value: [])
public func fetchData() {
let results = Section.getDummyData()
sectionList.accept(results)
}
}
class ViewController: UIViewController {
let viewModel = ViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
//trigger reloadData
viewModel.dataRelay
.subscribe(onNext: { [weak self] _ in
self?.tableView.reloadData()
})
.disposed(by: disposeBag)
// Trigger data fetching
viewModel.fetchData()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.dataRelay.value.count //here's the value
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = homeVM.dataRelay.value
switch data[indexPath.row] {
case .carousel(let title, _):
let cell = tableView.dequeueReusableCell(withIdentifier: CarouselTableView.identifier, for: indexPath) as! CarouselTableView
cell.title.text = title
return cell
case .standard(let title, _):
let cell = tableView.dequeueReusableCell(withIdentifier: StandardTableView.identifier, for: indexPath) as! StandardTableView
cell.title.text = title
return cell
}
}
}
Is this a valid and recommended approach, or should I create another variable to hold the data and update it within the subscription?
What are the potential pros and cons of directly accessing value in this scenario, and what considerations should I be aware of when working with RxSwift and UITableViews?
it's possible to use bind(to: ) with different TableViewCell without RxDataSources ?
Thanks for any guidance or insights!
There seems to be some confusion here... Best practice is to use bind(to:)
and you don't need to import RxDataSources in order to use it...
viewModel.dataRelay
.bind(to: tableView.rx.items) { tableView, row, item in
let indexPath = IndexPath(row: row, section: 0)
switch item {
case .carousel(let title, _):
let cell = tableView.dequeueReusableCell(withIdentifier: CarouselTableView.identifier, for: indexPath) as! CarouselTableView
cell.title.text = title
return cell
case .standard(let title, _):
let cell = tableView.dequeueReusableCell(withIdentifier: StandardTableView.identifier, for: indexPath) as! StandardTableView
cell.title.text = title
return cell
}
}
.disposed(by: disposeBag)
The above has the same behavior as what you are attempting and is standard practice.
Frankly, your ViewModel
should have a property that is an Observable
(or a Driver
) instead of a Relay
.
The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.
The fundamental problem with your code isn't that you are using .value
, it's that you aren't using reactive programming. (Sure, you are using the Rx library, but not in a reactive way.)
Use of Subject
s or Relay
s are only for very specific, and uncommon, needs.
When should I use a subject [or relay]? When all of the following are true:
- you don't have an observable or anything that can be converted into one.
- you require a hot observable.
- the scope of your observable is a type.
- you don't need to define a similar event and no similar event already exists.
-- Dave Sexton