首页 > 解决方案 > 如何在 MVVM 中使用 RxSwift 正确处理来自 api 请求的错误?

问题描述

所以,我有一个按钮,点击它会发出一个 API 请求。当 API 请求返回错误时,如果我的理解是正确的,序列将被终止,不会记录后续动作。如何正确处理此问题,以便在点击按钮时仍然可以发出另一个 API 请求。

我的想法是有两个我可以在ViewController中订阅并按下按钮的 observable,其中一个将打印成功响应,其中一个将打印错误。只是不太确定我怎么能做到这一点。

PS:在 Post.swift 中,我故意将id设置为String类型以使响应失败。它应该是一个Int类型。

Post.swift

import Foundation
struct Post: Codable {
    let id: String
    let title: String
    let body: String
    let userId: Int
}

APIClient.swift

class APIClient {

    static func request<T: Codable> (_ urlConvertible: URLRequestConvertible, decoder: JSONDecoder = JSONDecoder()) -> Observable<T> {

        return Observable<T>.create { observer in

            URLCache.shared.removeAllCachedResponses()

            let request = AF.request(urlConvertible)
                .responseDecodable (decoder: decoder) { (response: DataResponse<T>) in

                switch response.result {
                case .success(let value):
                    observer.onNext(value)
                    observer.onCompleted()
                case .failure(let error):
                    switch response.response?.statusCode {
                    default:
                        observer.onError(error)
                    }
                }
            }

            return Disposables.create {
                request.cancel()
            }
        }
    }

}

PostService.swift

class PostService {
    static func getPosts(userId: Int) -> Observable<[Post]> {
        return APIClient.request(PostRouter.getPosts(userId: userId))
    } 
}

ViewModel.swift

class LoginLandingViewModel {

    struct Input {
        let username: AnyObserver<String>
        let nextButtonDidTap: AnyObserver<Void>
    }

    struct Output {
        let apiOutput: Observable<Post>
        let invalidUsername: Observable<String>
    }

    // MARK: - Public properties

    let input: Input
    let output: Output

    // Inputs

    private let usernameSubject = BehaviorSubject(value: "")
    private let nextButtonDidTapSubject = PublishSubject<Void>()

    // MARK: - Init

    init() {

        let minUsernameLength = 4

        let usernameEntered = nextButtonDidTapSubject
            .withLatestFrom(usernameSubject.asObservable())

        let apiOutput = usernameEntered
            .filter { text in
                text.count >= minUsernameLength
            }
            .flatMapLatest { _ -> Observable<Post> in
                PostService.getPosts(userId: 1)
                    .map({ posts -> Post in
                        return posts[0]
                    })

            }

        let invalidUsername = usernameEntered
            .filter { text in
                text.count < minUsernameLength
            }
            .map { _ in "Please enter a valid username" }


        input = Input(username: usernameSubject.asObserver(),
                      nextButtonDidTap: nextButtonDidTapSubject.asObserver())

        output = Output(apiOutput: apiOutput,
                        invalidUsername: invalidUsername)

    }

    deinit {
        print("\(self) dellocated")
    }


}

视图控制器

private func configureBinding() {

        loginLandingView.usernameTextField.rx.text.orEmpty
            .bind(to: viewModel.input.username)
            .disposed(by: disposeBag)

        loginLandingView.nextButton.rx.tap
            .debounce(0.3, scheduler: MainScheduler.instance)
            .bind(to: viewModel.input.nextButtonDidTap)
            .disposed(by: disposeBag)

        viewModel.output.apiOutput
            .subscribe(onNext: { [unowned self] post in
                print("Valid username - Navigate with post: \(post)")
                })
            .disposed(by: disposeBag)


        viewModel.output.invalidUsername
            .subscribe(onNext: { [unowned self] message in
                self.showAlert(with: message)
            })
            .disposed(by: disposeBag)

    }

标签: swiftmvvmerror-handlingrx-swift

解决方案


您可以通过实现偶数序列来做到这一点:

第一步:在您的网络通话中使用.rx分机号URLSession.shared

func networkCall(...) -> Observable<[Post]> {
    var request: URLRequest = URLRequest(url: ...)
    request.httpMethod = "..."
    request.httpBody = ...

    URLSession.shared.rx.response(request)
        .map { (response, data) -> [Post] in
            guard let json = try? JSONSerialization.jsonObject(with: data, options: []),
                let jsonDictionary = json as? [[String: Any]]
                else { throw ... }    // Throw some error here

            // Decode this dictionary and initialize your array of posts here
            ...
            return posts
        }
}

第二步,具体化您的可观察序列

viewModel.networkCall(...)
    .materialize()
    .subscribe(onNext: { event in
        switch event {
            case .error(let error):
                // Do something with error
                break
            case .next(let posts):
                // Do something with posts
                break
            default: break
        }
    })
    .disposed(by: disposeBag)

这样,即使您在网络调用中抛出错误,您的可观察序列也永远不会终止,因为.error事件被转换为.next事件,但状态为.error.


推荐阅读