Обработка сетевой ошибки в сочетании с привязкой к tableView (Moya, RxSwift, RxCocoa)

В настоящее время я использую Moya для выполнения сетевых запросов. Я реализовал следующее из одного из примеров проектов @ https://github.com/DroidsOnRoids/RxSwiftExamples#tutorials

Ниже я настроил restaurantSearch, чтобы когда кто-то вводил текст, он делал новый запрос.

var restaurantSearch: Observable<(String)> {
    return searchBar
        .rx_text
        .throttle(0.5, scheduler: MainScheduler.instance)
        .distinctUntilChanged()
}

У меня есть метод, который возвращает наблюдаемое типа [Ресторан]

func restaurants() -> Observable<[Restaurant]> {
    return restaurantSearch
        .observeOn(MainScheduler.instance)
        .flatMapLatest { postcode -> Observable<[Restaurant]?> in
            return self.getRestaurants(postcode, cuisine: "", restaurantName: "")
        }.replaceNilWith([])
}

internal func getRestaurants(postcode: String, cuisine: String, restaurantName: String) -> Observable<[Restaurant]?> {
    return self.justEatProvider
        .request(.Restaurant(postcode, cuisine, restaurantName))
        .debug()
        .mapArrayOptional(Restaurant.self, keyPath: "Restaurants")
}

Я вызываю этот метод и привязываю его к tableView следующим образом:

func setupRx() {

    api = JustEatApi(provider: provider, restaurantSearch: restaurantSearch)

    api
        .restaurants()
        .bindTo(tableView.rx_itemsWithCellIdentifier("RestaurantTableViewCell", cellType: RestaurantTableViewCell.self)) { (row, element, cell) in
            cell.restaurant = element
        }
        .addDisposableTo(disposeBag)
}

Это нормально работает. Если я ввожу почтовый индекс, он выполняет поиск, и tableView заполняется.

Если я отключу Интернет и попытаюсь изменить почтовый индекс, tableView останется как есть. Однако, когда я прокручиваю, мое приложение вылетает со следующим:

@noreturn func rxFatalError(lastMessage: String) {
    // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
    fatalError(lastMessage)
}

Также, если я не прокручиваю, а просто включаю Интернет и меняю почтовый индекс, ничего не происходит. Похоже, он потерял привязку.

Сначала я попытался добавить catchOnError перед вызовом метода bindTo, но я прочитал здесь в комментарии, что он не должен обрабатываться как часть UIBinding: http://blog.scottlogic.com/2014/05/11/reactivecocoa-tableview-binding.html

Я предполагаю, что мне следует обработать это в методе:

func restaurants() -> Observable<[Restaurant]> {
    return restaurantSearch
        .observeOn(MainScheduler.instance)
        .flatMapLatest { postcode -> Observable<[Restaurant]?> in
            return self.getRestaurants(postcode, cuisine: "", restaurantName: "")
        }.replaceNilWith([])
}

Итак, у меня есть 2 вопроса:

1) Где и как мне обрабатывать сетевую ошибку?

2) Почему tableView не обновляется после того, как я снова подключусь к Интернету?

Любая помощь очень ценится.


person pls    schedule 28.04.2016    source источник


Ответы (2)


Я лично обрабатываю сетевые ошибки в сервисах, которые делают / анализируют сетевые запросы. Хороший способ сделать это - обернуть результаты вашей сети в какое-то перечисление (аналогично необязательному, но с ошибкой, если ноль). Итак, у вас будет что-то вроде:

enum APIResult<T> {
    case Success(T)
    case Error(ErrorType)
}

И тогда ваши сервисы вернут что-то вроде этого

Observable<APIResult<[Restaurant]>>

Если вы используете архитектуру MVVM, вы затем отфильтруете только успешные результаты в модели представления и предоставите эти данные для контроллера просмотра.

Также вам следует взглянуть на модуль Driver. Это модуль, специально созданный для привязок пользовательского интерфейса, поэтому он подписывается только на основной поток, никогда не ошибается и не передает последний результат.

Чтобы перевести Observable в Driver, вы используете один из трех методов:

func asDriver(onErrorJustReturn onErrorJustReturn: Self.E) -> RxCocoa.Driver<Self.E>
func asDriver(onErrorDriveWith onErrorDriveWith: RxCocoa.Driver<Self.E>) -> RxCocoa.Driver<Self.E>
func asDriver(onErrorRecover onErrorRecover: (error: ErrorType) -> RxCocoa.Driver<Self.E>) -> RxCocoa.Driver<Self.E>

Этот подход автоматически решит ваш второй вопрос, потому что, когда Observable выдает ошибку, все подписчики отказываются от подписки, т.е. он завершает поток. Попробуйте поместить .debug () после вызова api.restaurants () и сами убедитесь, что он отписывается.

Подробнее о драйвере и других модулях можно узнать здесь

person muvaaa    schedule 11.05.2016
comment
Хороший! Лично, когда я использую модели представления, у меня также есть эта переменная в модели представления, которая сообщает об ошибках, поэтому контроллер может прослушивать ее как независимую единицу. Помимо этого APIResult великолепен, вы также можете использовать для него Result. - person sunshinejr; 19.05.2016

Просто добавьте catchErrorJustReturn ([]) перед привязкой таблицы.

person dfmarulanda    schedule 22.01.2019