顯然我是RxSwift的新手,雖然我消耗了大量文檔和演講,但我想我缺少一些基本概念。RxSwift可觀察錯誤停止鏈 - 帶Rx的Web服務,如何恢復?
在我的應用程序中,我有一個REST風格的Web服務來加載各種資源,但Web服務的基礎URL在構建/開始時未知。相反,我有一個「URL解析器」Web服務,我可以使用我的應用程序包,版本和可能的環境(「生產」,「調試」或在應用程序調試設置中輸入的任何自定義字符串)調用以獲取基礎URL,然後使用爲實際的服務。
我的想法是,我會創建2個服務,一個用於URL解析器,另一個用於實際的Web服務,它爲我提供了資源。 URL解析器將有一個Variable和一個Observable。我使用該變量來表示需要通過對URL解析器的Web服務調用刷新基礎URL。我通過觀察變量並僅對真實值進行過濾來實現此目的。服務類中的函數將變量值設置爲true(最初爲false),並在已過濾變量的觀察者中,在另一個Observable中進行Web服務調用(此示例使用虛擬JSON Web服務):
import Foundation
import RxSwift
import Alamofire
struct BaseURL: Codable {
let title: String
}
struct URLService {
private static var counter = 0
private static let urlVariable: Variable<Bool> = Variable(false)
static let urlObservable: Observable<BaseURL> = urlVariable.asObservable()
.filter { counter += 1; return $0 }
.flatMap { _ in
return Observable.create { observer in
let url = counter < 5 ? "https://jsonplaceholder.typicode.com/posts" : ""
let requestReference = Alamofire.request(url).responseJSON { response in
do {
let items = try JSONDecoder().decode([BaseURL].self, from: response.data!)
observer.onNext(items[0])
} catch {
observer.onError(error)
}
}
return Disposables.create() {
requestReference.cancel()
}
}
}
static func getBaseUrl() {
urlVariable.value = true;
}
static func reset() {
counter = 0;
}
}
現在的問題是,有時可能會發生Web服務調用失敗,我需要向用戶顯示錯誤,以便重試。我認爲onError對此有用,但它似乎永遠殺死所有用戶。
我可以把訂閱在它自己的功能和觀察者的錯誤處理程序中,我可以顯示一個警告,然後再次調用訂閱功能,像這樣:
func subscribe() {
URLService.urlObservable.subscribe(onNext: { (baseURL) in
let alert = UIAlertController(title: "Success in Web Service", message: "Base URL is \(baseURL.title)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Try again!", style: .default, handler: { action in
URLService.getBaseUrl()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}, onError: { error in
let alert = UIAlertController(title: "Error in Web Service", message: "Something went wrong: \(error.localizedDescription)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Yes", style: .default, handler: { action in
URLService.reset()
self.subscribe()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
VesselService.reset()
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}).disposed(by: disposeBag)
}
然後在我的AppDelegate我會調用
subscribe()
URLService.getBaseUrl()
的問題是,所有其他觀察員喪命上的錯誤,以及但是因爲在URLService.urlObservable的唯一的其他觀察者是我的其他網絡服務類,我想我可以實現同樣的風格認購功能也在那裏。
我讀過一些人建議返回一個Result枚舉,它有兩種情況:實際結果(.success(result:T))或錯誤(.error(error:Error))。
那麼,在Rx中處理錯誤Web服務錯誤的更好方法是什麼?我不能把這個問題包裹起來,我想了解2天。任何想法或建議?
更新
它只是來到我的腦海,我可以忽略來自Web服務的錯誤調用的任何錯誤完全,而是張貼到一個全球性的「錯誤」的變量,我的應用程序代理可以觀察到顯示警報。 「錯誤」可以引用最初導致它的功能,因此可以進行重試。我仍然感到困惑,不知道該怎麼做。 :/
更新2
我想我可能找到了一個工作方案。由於我還是Rx和RxSwift的初學者,我很樂意接受改進建議。當我寫實際的代碼,我分裂我的調用鏈分爲兩個部分:
- ,我讓Web服務調用
- ,我點擊一個按鈕,處理Web服務的結果的一部分,部分,無論是錯誤還是成功
在我單擊按鈕並處理結果的部分中,我使用catchError並按照註釋中的建議重試。代碼如下所示:
let userObservable = URLService
.getBaseUrl(environment: UserDefaults.standard.environment) //Get base url from web service 1
.flatMap({ [unowned self] baseURL -> Observable<User> in
UserService.getUser(baseURL: baseURL,
email: self.usernameTextField.text!,
password: self.passwordTextField.text!) //Get user from web service 2 using the base url from webservice 1
})
signInButton
.rx
.tap
.throttle(0.5, scheduler: MainScheduler.instance)
.flatMap({ [unowned self]() -> Observable<()> in
Observable.create { observable in
let hud = MBProgressHUD.present(withTitle: "Signing in...");
self.hud = hud
observable.onNext(())
return Disposables.create {
hud?.dismiss()
}
}
})
.flatMap({() -> Observable<User> in
return userObservable
})
.catchError({ [unowned self] error -> Observable<User> in
self.hud?.dismiss()
self.handleError(error)
return userObservable
})
.retry()
.subscribe(onNext: { [unowned self] (user) in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
self.hud?.dismiss()
})
.disposed(by: disposeBag)
訣竅是移動調用兩個Web服務了該隱到自己的變量,所以我可以在任何時候重新調用它。當我現在返回「userObservable」並在Web服務調用期間發生錯誤時,我可以在catchError中顯示錯誤,併爲下一次重試返回相同的「userObservable」。
目前這隻能正確處理錯誤,當他們發生在Web服務調用鏈,所以我認爲我應該讓按鈕點擊驅動程序。
「問題是所有其他觀察者都因爲錯誤而死亡」 - 這可能是一個問題,但它是Rx設計的方式。一個可觀察的**可能有零個或多個OnNext,並且可能只有一個OnError或OnCompleted,此時觀察結束,並且不能返回任何更多值**。 – Enigmativity
您通常會使用帶**重試**的** catch **運算符來捕獲錯誤並重試observables。 – Enigmativity
啊,我會看看並重試。謝謝。我知道這是設計,所以我想知道這個:我可以在我的Web服務中創建1個觀察者。然後,我可以訂閱觀察員,轉換其數據並僅在成功時將其發送給我的變量。然後,我可以創建第二個類似的轉換,我將返回。只有在轉換成功的情況下,變量纔會被更新,而返回會將錯誤升級到我的UI,因此用戶可以處理錯誤。這是一種有效的方法嗎? – xxtesaxx