RXSwift + Moya + Обработка ошибок + Кнопка обновления

Я пытаюсь настроить табличное представление, которое обновляет пользовательские данные после нажатия кнопки. RXSwift используется для всей цепочки событий. Моя используется для маршрутизации.

Я пытаюсь использовать стандартную обработку ошибок, предоставленную Moya, а именно:

provider.rx.request(.userProfile("ashfurrow")).subscribe { event in
    switch event {
    case let .success(response):
        image = UIImage(data: response.data)
    case let .error(error):
        print(error)
    }
}

Единственный способ заставить это работать - использовать внутренний метод подписки. См. Код ниже. Может ли кто-нибудь придумать способ, не требующий внутренней подписки? Это и так кажется немного неуклюжим.

class ViewController: UIViewController {
    @IBOutlet weak var refreshBtn: UIButton!
    @IBOutlet weak var tableView: UITableView!

    let provider = MoyaProvider<MyAPI>()

    let disposeBag = DisposeBag()

    var latestUsers = Variable<[User]>([])

    override func viewDidLoad() {
        super.viewDidLoad()

        setupObservableBtnRefreshWithDataFetch()
        bindDataToTableView()
    }

    func setupObservableBtnRefreshWithDataFetch() {
        let refreshStream = refreshBtn.rx.tap.startWith(())

        let responseStream = refreshStream.flatMapLatest { _ -> SharedSequence<DriverSharingStrategy, [User]> in
            let request = self.provider.rx.request(.showUsers)

            // Inner Subscribe here, to be able to use the standard Moya subscribe methods for error handling
            request.subscribe { event in
                switch event {
                case .success(let user):
                    print("Success")
                case .error(let error):
                    print("Error occurred: \(error.localizedDescription)")
                }
            }

            return request
                .filterSuccessfulStatusCodes()
                .map([User].self)
                .asDriver(onErrorJustReturn: [])
        }

        let nilOnRefreshTapStream: Observable<[User]> = refreshBtn.rx.tap.map { _ in return [] }
        let tableDisplayStream = Observable.of(responseStream, nilOnRefreshTapStream)
            .merge()
            .startWith([])

        tableDisplayStream
            .subscribe { event in
                switch event {
                case .next(let users):
                    print("Users are:")
                    print(users)
                    self.latestUsers.value = users
                    break
                case .completed:
                    break
                case .error(let error):
                    print("Error occurred: \(error.localizedDescription)")
                    break
                }
            }
            .disposed(by: self.disposeBag)
    }

    func bindDataToTableView() {
        latestUsers.asObservable()
            .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (_, model: User, cell: UITableViewCell) in
                cell.textLabel?.text = model.login
            }
            .disposed(by: disposeBag)
    }
}      

class User: Decodable {
    var name: String?
    var mobile: Int?
    var userRequestedTime: String?
    var login: String?

    init(name: String, mobile: Int, login: String = "") {
        self.name = name
        self.mobile = mobile
        self.login = login
    }
}

person Rowan Gontier    schedule 12.10.2018    source источник
comment
Я не знаком с Мойей, но двойная подписка на request (как ваша ручная подписка, так и любая подписка на возвращаемое значение refreshStream.flatMap) не кажется правильным. Что произойдет, если вы опустите ручную подписку?   -  person CloakedEddy    schedule 22.10.2018
comment
поля, доступные в событии подписки, изменяются (например, .success изменяется до завершения). Хотя работает хорошо, как и я.   -  person Rowan Gontier    schedule 23.10.2018


Ответы (1)


Я исследовал Мойю и узнал, что это оболочка для сетевых операций.

Не совсем понятно, для какой цели служит внутренняя подписка - насколько я понимаю, она запускает идентичный, но отдельный сетевой запрос, который не должен влиять на подписку на другой запрос. Также кажется, что касание refreshButton испускает два элемента в tableDisplayStream (из responseStream (из refreshStream) и nilOnRefreshTapStream).

Обратите внимание, что переменная устарела. Лично я также предпочитаю .debug().subscribe() распечатывать события вручную при закрытии подписки.

Исходя из этого, я бы написал следующий код. Я не тестировал. Надеюсь, это поможет!

class ViewController: UIViewController {
    // ...

    private let provider = MoyaProvider<MyAPI>()
    private let disposeBag = DisposeBag()

    /// Variable<T> is deprecated; use BehaviorRelay instead
    private let users = BehaviorRelay<[User]>(value: [])

    private func setupObservableBtnRefreshWithDataFetch() {
        refreshBtn.rx.tap
           .startWith(()) // trigger initial load
           .flatMapLatest { _ in 
               self.provider.rx.request(.showUsers)
                   .debug("moya request")
                   .filterSuccessfulStatusCodes()
                   .map([User].self)
                   .asDriver(onErrorJustReturn: []) // don't let the error escape
           } 
           .drive(users)
           .disposed(by: disposeBag)
    }

    private func bindDataToTableView() {
        users
            .asDriver()
            .debug("driving table view ")
            .drive(tableView.rx.items /* ... */)
            .disposed(by: disposeBag)
    }
}      
person CloakedEddy    schedule 23.10.2018
comment
Спасибо. Основная проблема, с которой я сталкиваюсь, - это обработка случаев успеха / ошибки. Кажется, у Moya есть определенный набор обработчиков, когда вы подписываетесь на его запрос по сравнению с обычным RXSwift. Меня особенно интересовал успех, а не просто полный, поэтому мне пришлось использовать внутреннюю подписку. - person Rowan Gontier; 24.10.2018
comment
Метод Moya .request(_) возвращает Single<Response>, который представляет собой особый вид Observable, который является частью RxSwift. Можете ли вы опубликовать свой код для MyAPI, чтобы мы могли попытаться воспроизвести описанное вами поведение? - person CloakedEddy; 25.10.2018