Сеть с RxSwift

Я пытался понять rxSwift. Я столкнулся с проблемой запроса и хочу реализовать это хорошо. В настоящее время я использую этот код:

enum RequestState<T> {
    case loading
    case loaded(T)
    case error(Error)
}

struct Response<T: Decodable>: Decodable {
    let data: T
    let error: ResponseError?
}

searchBar.rx.text.asObservable()
    .flatMap { self.provider.rx.request(Request(query: $0)) }
    .map({ RequestState<Response<Bool>>.loaded($0) })
    .asDriver(onErrorRecover: { error in
        return Driver.just(.error(error))
    })
    .startWith(.loading)
    .drive(onNext: { state in
        switch state {
        case .loading: ()
        case .loaded(let response): ()
        case .error(let error): ()
        }
    })
    .disposed(by: disposeBag)

Это работает хорошо, но не слишком удобно для работы с данными и состоянием запроса. Я видел в демонстрационном проекте rxSwift следующий код.

struct RequestState<T: Decodable> {
    let isLoading: Bool
    let data: T
    let error: ResponseError?
}

let state = viewModel.requestMethod()

state
    .map { $0.isLoading }
    .drive(self.loaderView.isOffline)
    .disposed(by: disposeBag)

state
    .map { $0.data }
    .drive(tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

state
    .map { $0.error }
    .drive(onNext: { error in
        showAlert(error)
    })
    .disposed(by: disposeBag)

И моя проблема в следующем методе, я не могу понять магию Rx здесь:

func requestMethod() -> Driver<RequestState> {
    // supper code
}

Может кто-нибудь посоветует мне, что мне здесь делать?


person Artem Krachulov    schedule 06.10.2018    source источник
comment
В каком демонстрационном проекте вы это видели?   -  person Daniel T.    schedule 06.10.2018


Ответы (1)


Вот что я получил, глядя на оба ваших образца кода:

Во-первых, вот смысл использования:

    let request = searchBar.rx.text
        .unwrap()
        .map { URLRequest.search(forQuery: $0) }

    let networkRequest = createRequest(forType: MyType.self)
    let state = request
        .flatMap(networkRequest)

    state
        .map { $0.isLoading }
        .bind(to: loaderView.isOffline)
        .disposed(by: bag)

    state
        .map { $0.data }
        .unwrap()
        .bind(to: tableView.rx.items(dataSource: dataSource))
        .disposed(by: bag)

    state
        .map { $0.error }
        .unwrap()
        .subscribe(onNext: showAlert)
        .disposed(by: bag)

Вот код поддержки для вышеуказанного:

enum RequestState<T> {
    case loading
    case loaded(T)
    case error(Error)

    var isLoading: Bool {
        guard case .loading = self else { return false }
        return true
    }

    var data: T? {
        guard case let .loaded(t) = self else { return nil }
        return t
    }

    var error: Error? {
        guard case let .error(e) = self else { return nil }
        return e
    }
}

Вы увидите, что приведенное выше RequestState перечисление является чем-то вроде объединения обоих RequestState типов, которые вы показали в своем примере. Перечисление упрощает создание объекта, а вычисленные свойства упрощают извлечение информации.

func createRequest<T>(forType type: T.Type, session: URLSession = URLSession.shared) -> (URLRequest) -> Observable<RequestState<T>> where T: Decodable {
    return { request in
        return Observable.create { observer in
            observer.onNext(.loading)
            let disposable = session.rx.data(request: request)
                .subscribe { event in
                    switch event {
                    case let .error(error):
                        observer.onNext(.error(error))
                    case let .next(data):
                        do {
                            let item = try JSONDecoder().decode(type, from: data)
                            observer.onNext(.loaded(item))
                        }
                        catch {
                            observer.onNext(.error(error))
                        }
                    case .completed:
                        observer.onCompleted()
                    }
            }
            return Disposables.create([disposable])
        }
    }
}

Вышеуказанная функция является заводской. Вы используете его для создания функции, которая знает, как делать сетевые запросы для соответствующего типа. Вспомните, в коде, где он использовался, у меня было let networkRequest = createRequest(forType: MyType.self). Эта строка создает функцию networkRequest, которая принимает URLRequest и возвращает Observable, специализированный для рассматриваемого типа.

Когда Observable от networkRequest подписан, он немедленно отправит .loading case, а затем сделает запрос. Затем он будет использовать ответ, чтобы выдать .loaded(T) или .error(Error) в зависимости от результатов.

Лично я более склонен использовать вместо этого что-то вроде системы ActivityIndicator из примеров в репозитории RxSwift.

person Daniel T.    schedule 06.10.2018
comment
Спасибо за ваш пример. Но я использую сетевой менеджер Moya, а также хочу использовать метод запроса Moya self.provider.rx.request(Request(query: $0) с возвращаемым параметром Single<Response>, а затем работать с ними, вместо того, чтобы писать свой собственный метод запроса или расширение для Moya. - person Artem Krachulov; 07.10.2018