swift - 如何在 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)
}
解决方案
您可以通过实现偶数序列来做到这一点:
第一步:在您的网络通话中使用.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
.
推荐阅读
- wpf - 将图标添加到带有样式的菜单中
- android - AndroidRuntime:java.lang.RuntimeException:无法实例化活动 ComponentInfo{.../....MainActivity}:java.lang.ClassNotFoundException:
- android - 自定义按钮 - 为什么带有 png 图像的 StateListDrawable 摆脱了默认按钮 StateListAnimator,而 xml 形状保留了它?
- python - setuptools 何时将可编辑链接安装到 /.../my_module/build/lib/ 以及何时链接到 /.../my_module?
- asp.net-mvc - IIS express 未运行 MVC 站点,未在 applicationhost.config 中创建站点
- ios - Swift iOS - 如何在单元格的 superView 中获取按钮框架
- php - `where in` 只取存储过程 mysql 中的第一个值
- android - 在 Android 中执行 oAuth 1.0 API
- javascript - Wordpress Owl Carousel 不显示自定义帖子类型
- node.js - 3.4版本的mongodb不区分大小写索引