前言
在之前的文章Moya+PromiseKit+RxSwift優雅的書寫網絡請求中,我們嘗試了使用PromiseKit和RxSwift共同實現網絡請求,在后來我個人的嘗試中發現了問題,遂撰文記之。
問題描述
PromiseKit的閉包只會執行一次。
環境配置
- Xcode 8.3
- Swift 3
實例
這一切都是由于實現一個帶有緩存的網絡請求引起的。為此我們實現一個RxMoyaProvider的Extension,用于實現帶有緩存的網絡請求。
extension RxMoyaProvider {
func offLineCacheRequest(_ token: Target) -> Observable<Response> {
return Observable.create({[weak self] (observer) -> Disposable in
// 1. 在這里我們讀取本地緩存中的數據,若有緩存,則返回緩存數據
// 偽代碼
if 存在緩存 {
observer.onNext(緩存數據)
}
//2 .進行正常的網絡請求
let cancellableToken = self?.request(token) { result in
switch result {
case let .success(response):
// 3. 返回請求后的最新數據
observer.onNext(response)
observer.onCompleted()
// 4. 緩存并覆蓋舊數據
// 偽代碼
緩存數據
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create {
cancellableToken?.cancel()
}
})
}
}
我們基于剛剛實現的這個拓展再實現一個網絡請求。注意此處成功的回調result。
func getHomepagePageDataWithCache() -> Promise<HomepageData> {
return Promise(resolvers: { (result, error) in
provider.offLineCacheRequest(.frontpage)
.distinctUntilChanged()
.filterSuccessfulStatusCodes()
.mapJSON()
.mapObject(type: HomepageData.self)
.subscribe(onNext: {
result($0) //此處為PromiseKit的成功回調
}, onError: {
error($0)
})
.addDisposableTo(disposeBag)
})
}
然鵝就在這里出現問題了,當我們調用這個方法時:
viewModel.getHomepagePageDataWithCache().then {
print($0.packages?.last?.head ?? "")
}.catch {
print($0)
}
第一次調用因為本地沒有緩存,所以打印print($0.packages?.last?.head ?? "")只會調用一次,然鵝再次運行,存在本地緩存的情況下,該打印語句依然只執行一次。
經過打斷點,我發現相關代碼均已經執行:
// 請求成功前
if 存在緩存 {
observer.onNext(緩存數據)
}
// 請求成功后
observer.onNext(response)
以上兩次 observer.onNext都觸發了RxSwift訂閱,斷點也會停留在訂閱里面PromiseKit的閉包result上:
.subscribe(onNext: {
result($0)
},
但是在最終的閉包里只執行了一次打印:
then {
print($0.packages?.last?.head ?? "")
}.
原因
經過查詢資料,原因如下:
PromiseKit 不具備流的特性,即不支持依賴時間順序依次傳遞值,換句話說就是調用閉包 result多次也只能執行一次。這就沒辦法讓我們以完整的聲明式的寫法完成需求。
所以要想兩次都觸發并執行RxSwift的訂閱,就不能使用PromiseKit來實現這個網絡請求。處理很簡單,就是把PromiseKit里面實現網絡請求的部分提出來改寫即可。
provider.offLineCacheRequest(.frontpage)
.distinctUntilChanged()
.filterSuccessfulStatusCodes()
.mapJSON()
.mapObject(type: HomepageData.self)
我們把這個mapObject后的Observable返回即可,讓后續的操作訂閱它。