我正在尝试使用Combine
框架创建网络层。我已经实现了一些功能,效果很好。其中一些api调用需要access token
,并且有一些调用来获取和刷新此令牌。现在我面临的问题是,我希望需要此令牌的函数在执行之前自动尝试刷新它-仅在需要刷新或出现错误时才这样做。现在我有这样的功能:
static func login(with parameters: CredentialsRequest) -> AnyPublisher<LoggedUser, RequestError>
static func refreshToken(_ token: Token) -> AnyPublisher<Token, RequestError>
static func activeGames(token: Token) -> AnyPublisher<[Game], RequestError>
令牌具有属性:requiresRefresh: Bool
我如何使函数activeGames
仍返回类型AnyPublisher<[Game], RequestError>
,但以这种方式工作,即如果首先检查令牌是否需要刷新,如果需要,则在刷新之前等待它?
一种方法是取消任何将令牌作为参数接收的函数:
func authenticated<P: Publisher, T>(_ source: @escaping (Token) -> P) -> (Token) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token in
let first: AnyPublisher<Token, RequestError>
if token.requiresRefresh {
// token needs refresh, let's create the refresh operation
first = refreshToken(token)
} else {
// token doesn't need refresh, just forward it downstream
// note, there might be an easier way for this than with a Future
first = Future<Token, RequestError> { $0(.success(token)) }.eraseToAnyPublisher()
}
// now, the Publisher chain
return first
.flatMap(source)
.catch { (error) -> AnyPublisher<T, RequestError> in
// assuming this error corresponds to the invalid/expired token
if case .invalidToken = error {
// and in this case we redo the authentication and the actual request
return refreshToken(token).flatMap(source).eraseToAnyPublisher()
} else {
// report the error otherwise
return Fail<T, RequestError>(error: error).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
}
然后您将像authenticated(activeGames)(token)
这样使用它,因此基本上调用提升函数的结果,而不是直接调用该函数。
某种程度上更简单的解决方案将涉及对最低级别的功能进行操作,例如在URLSession
堆栈上方的功能,但是制定此解决方案需要了解您的网络堆栈实现。