【发布时间】:2021-01-10 22:52:58
【问题描述】:
我的 NetworkManager 类中有这个通用的fetchData() 函数,它能够请求向网络发出授权请求,如果它失败(在多次重试后)发出一个错误,将重新启动我的应用程序(请求一个新的登录)。我需要同步调用这个重试令牌,我的意思是,如果多个请求失败,那么一次应该只有一个请求刷新令牌。如果那个失败,另一个请求必须被丢弃。我已经尝试过使用 DispatchGroup / NSRecursiveLock / 以及调用下面描述的函数 cancelRequests 的方法(在这种情况下,任务计数始终为 0)。我怎样才能使这种行为在这种情况下起作用?
- 我的 NetworkManager 类:
public func fetchData<Type: Decodable>(fromApi api: TargetType,
decodeFromKeyPath keyPath: String? = nil) -> Single<Response> {
let request = MultiTarget(api)
return provider.rx.request(request)
.asRetriableAuthenticated(target: request)
}
func cancelAllRequests(){
if #available(iOS 9.0, *) {
DefaultAlamofireManager
.sharedManager
.session
.getAllTasks { (tasks) in
tasks.forEach{ $0.cancel() }
}
} else {
DefaultAlamofireManager
.sharedManager
.session
.getTasksWithCompletionHandler { (sessionDataTask, uploadData, downloadData) in
sessionDataTask.forEach { $0.cancel() }
uploadData.forEach { $0.cancel() }
downloadData.forEach { $0.cancel() }
}
}
}
- 使重试有效的单一扩展:
public extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
private var refreshTokenParameters: TokenParameters {
TokenParameters(clientId: "pdappclient",
grantType: "refresh_token",
refreshToken: KeychainManager.shared.refreshToken)
}
func retryWithToken(target: MultiTarget) -> Single<E> {
self.catchError { error -> Single<Response> in
if case Moya.MoyaError.statusCode(let response) = error {
if self.isTokenExpiredError(error) {
return Single.error(error)
} else {
return self.parseError(response: response)
}
}
return Single.error(error)
}
.retryToken(target: target)
.catchError { error -> Single<Response> in
if case Moya.MoyaError.statusCode(let response) = error {
return self.parseError(response: response)
}
return Single.error(InvalidGrantException())
}
}
private func retryToken(target: MultiTarget) -> Single<E> {
let maxRetries = 1
return self.retryWhen({ error in
error
.enumerated()
.flatMap { (attempt, error) -> Observable<Int> in
if attempt >= maxRetries {
return Observable.error(error)
}
if self.isTokenExpiredError(error) {
return Observable<Int>.just(attempt + 1)
}
return Observable.error(error)
}
.flatMap { _ -> Single<TokenResponse> in
self.refreshTokenRequest()
}
.share()
.asObservable()
})
}
private func refreshTokenRequest() -> Single<TokenResponse> {
return NetworkManager.shared.fetchData(fromApi: IdentityServerAPI
.token(parameters: self.refreshTokenParameters)).do(onSuccess: { tokenResponse in
KeychainManager.shared.accessToken = tokenResponse.accessToken
KeychainManager.shared.refreshToken = tokenResponse.refreshToken
}, onError: { error in
NetworkManager.shared.cancelAllRequests()
})
}
func parseError<E>(response: Response) -> Single<E> {
if response.statusCode == 401 {
// TODO
}
let decoder = JSONDecoder()
if let errors = try? response.map([BaseResponseError].self, atKeyPath: "errors", using: decoder,
failsOnEmptyData: true) {
return Single.error(BaseAPIErrorResponse(errors: errors))
}
return Single.error(APIError2.unknown)
}
func isTokenExpiredError(_ error: Error) -> Bool {
if let moyaError = error as? MoyaError {
switch moyaError {
case .statusCode(let response):
if response.statusCode != 401 {
return false
} else if response.data.count == 0 {
return true
}
default:
break
}
}
return false
}
func filterUnauthorized() -> Single<E> {
flatMap { (response) -> Single<E> in
if 200...299 ~= response.statusCode {
return Single.just(response)
} else if response.statusCode == 404 {
return Single.just(response)
} else {
return Single.error(MoyaError.statusCode(response))
}
}
}
func asRetriableAuthenticated(target: MultiTarget) -> Single<Element> {
filterUnauthorized()
.retryWithToken(target: target)
.filterStatusCode()
}
func filterStatusCode() -> Single<E> {
flatMap { (response) -> Single<E> in
if 200...299 ~= response.statusCode {
return Single.just(response)
} else {
return self.parseError(response: response)
}
}
}
}
【问题讨论】:
-
您在寻找反应式解决方案吗?
-
最好。我的应用完全建立在 RxSwift 之上。但如果你可以在没有 Rx 的情况下提出建议,我也会接受这个建议。
标签: swift alamofire rx-swift moya