Search code examples
iosswiftuitableviewrx-swiftfrp

Tableview not receiving signals from Driver


I have the following MVVM-C + RxSwift code.

The problem is that the TableView is not receiving any signals. When I debug the results I can see that the API call is returning what it should, the objects array is populated with objects but the tableview does not show any results. Here is the console output:

2018-11-13 16:12:08.107: searchText -> Event next(qwerty)
Search something: qwerty
2018-11-13 16:12:08.324: viewModel.data -> Event next([])

Could it be the tableview itself? Maybe wrong custom cell setup?

ViewController.swift:

    tableView = UITableView(frame: self.view.frame)
    tableView.delegate = nil
    tableView.dataSource = nil
    tableView.register(SearchResultCell.self, forCellReuseIdentifier: "SearchResultCell")

    viewModel.data
        .debug("viewModel.data", trimOutput: false)
        .drive(tableView.rx.items(cellIdentifier: "SearchResultCell")) { row, object, cell in

            cell.name.text = object.name
            cell.something.text = object.something
        }
        .disposed(by: disposeBag)

ViewModel.swift:

let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "something to search for")

lazy var data: Driver<[Object]> = {
    return self.searchText.asObservable()
        .debug("searchText", trimOutput: false)
        .throttle(0.3, scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest(searchSomething)
        .asDriver(onErrorJustReturn: [])
}()

func searchSomething(query: String) -> Observable<[Object]> {

    print("Search something: \(query)")

    let provider = MoyaProvider<APIService>()

    var objects = [Object]()

    provider.rx.request(.search(query: query)).subscribe { event in
        switch event {
        case let .success(response):
            do {
                let responseJSON: NSDictionary = try (response.mapJSON() as? NSDictionary)!
                objects = self.parse(json: responseJSON["results"] as Any)

            } catch(let error) {
                print(error)
            }

            break
        case let .error(error):
            print(error)
            break
        }
    }
    .disposed(by: disposeBag)

    let result: Observable<[Object]> = Observable.from(optional: objects)

    return result
}

Solution

    • When using flatMap, you do not want to create nested subscriptions. You will create an Observable that returns the expected result, and flatMap will take care of subscribing to it. In the current state of things, searchSomething will always return an empty array, as Observable.from(optional: objects) will be called before the request has a chance to complete.
    • Since version 10.0 of Moya, provider will cancel the requests it created when deallocated. Here, it will be deallocated when execution exits searchSomething, hence the network request won't have time to finish. Moving provider's declaration to the view model's level solves this issue.

    Here's searchSomething(query: String) -> Observable<[Object]> rewritten.

    let provider = MoyaProvider<APIService>()
    
    func searchSomething(query: String) -> Observable<[Object]> {
    
        print("Search something: \(query)")
    
        return provider.rx.request(.search(query: query)).map { (response) -> [Object] in
            let responseJSON: NSDictionary = try (response.mapJSON() as? NSDictionary)!
    
            return self.parse(json: responseJSON["results"] as Any)
        }
    }
    

    Instead of doing the transformation in subscribe, it's done in map, which will be called for every next event, being passed the value associated with the event.