How to transform rx_tap of UIButton to a network r

2020-08-01 00:35发布

问题:

Suppose that I have a UIButton loginButton, I want to send a network request while tapping the button with the following code:

override func viewDidLoad() {
    super.viewDidLoad()

    let session = self.session // NSURLSession
    loginButton.rx_tap.subscribeNext { [unowned self] in
        session.rx_response(myRequest).subscribe { event in
            switch event {
            case .Next(let data, let response):
                // Handling Response
            case .Error(let error):
                // Handling Error
            default:
                return
            }
        }.addDisposableTo(disposeBag)
     }.addDisposableTo(disposeBag)
}

And in such case I can resend the request by tapping the button even if an error occurred with the network request.

Although the code works very well, I thought it is a little bit ugly due to the nested subscription. I tried the flatMap method to flatten the subscription:

loginButton.rx_tap
    .flatMap {
        return session.rx_response(myRequest)
    }
    .subscribe { event in
        switch event {
        case .Next(let data, let response):
            print("Next")
        case .Error(let error):
            print(error)
        default:
            return
        }
     }
     .addDisposableTo(disposeBag)

It seems that the two snippets above are of different logic. The latter subscription only works while no error happened just like normal subscription rather than subscribe the network request every time the button has been tapped.

Is there any way to flatten the formal snippet?


Added a snippet of nested subscription:

loginButton.rx_tap
    .debug("LoginButtonTapped")
    .subscribeNext {
        let disposable = session.rx_response(myRequest)
            .debug("AuthorizationRequest")
            .subscribe(
                onNext: { [unowned self] data, response in
                    // Handling Response
            },
                onError: { [unowned self] error in
                    // Showing Error
            })

        disposable.addDisposableTo(self.disposeBag)

        let alert = UIAlertController(title: "Please Wait.", message: "Requesting Tokens", preferredStyle: .Alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            disposable.dispose()
            alert.dismissViewControllerAnimated(true, completion: nil)
        })
        self.presentViewController(alert, animated: true, completion: nil)
    }.addDisposableTo(disposeBag)

Error can be caught using the following code:

let disposable = loginButton.rx_tap
    .map { session.rx_response(request) }
    .flatMap { [unowned self] in $0.catchError(self.presentError) }
    .subscribeNext { data, response in
        // Handling Response
    }

I also need to cancel the network request if necessary. If I manually dispose the disposable in the above snippet, the subscription will be disposed and I can not send the request again.

回答1:

You have 2 ways to achieve the requested behaviour. The first one is to user map and then switchLatest, this is the classic way. The second one is to user flatMap if you need to catch/retry in the network request sequence.

Ash Furrow has a very nice example in a workshop demo code using the second example and Moya:

    submitButton.rx_tap.map { _ -> Observable<MoyaResponse> in
        return provider.request(.Image)
    }.flatMap() { obs in
        return obs.filterSuccessfulStatusCodes()
            .mapImage()
            .catchError(self.presentError)
            .filter({ (thing) -> Bool in
                return thing != nil
            })
    }
    .take(1)
    .bindTo(imageView.rx_image)
    .addDisposableTo(disposeBag)

Alternatively, you can check the Github example in the RxSwift repository to see how these sort of things are handled.