swiftuikittableviewrx-swiftreactive

It is valid Accessing BehaviorRelay value directly for UITableView in RxSwift?


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!


Solution

  • 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.

    -- Heinrich Apfelmus

    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 Subjects or Relays 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