【发布时间】:2021-11-02 13:03:13
【问题描述】:
我实现了一个 API 服务处理程序,它在请求的标头中使用身份验证令牌。当用户在应用程序启动时登录时获取此令牌。 30 分钟后,令牌过期。因此,当在此时间跨度之后发出请求时,API 会返回 403 状态码。然后 API 应该再次登录并重新启动当前的 API 请求。
我遇到的问题是获取新令牌的登录函数使用完成处理程序让调用代码知道异步登录过程是否成功。当 API 获得 403 状态码时,它会调用登录过程,并且当它完成时,它应该再次发出当前请求。但是这个重复的 API 请求应该再次返回一些值。但是,在完成块中返回值是不可能的。有谁知道整个问题的解决方案?
登录功能如下:
func login (completion: @escaping (Bool) -> Void) {
self.loginState = .loading
let preparedBody = APIPrepper.prepBody(parametersDict: ["username": self.credentials.username, "password": self.credentials.password])
let cancellable = service.request(ofType: UserLogin.self, from: .login, body: preparedBody).sink { res in
switch res {
case .finished:
if self.loginResult.token != nil {
self.loginState = .success
self.token.token = self.loginResult.token!
_ = KeychainStorage.saveCredentials(self.credentials)
_ = KeychainStorage.saveAPIToken(self.token)
completion(true)
}
else {
(self.banner.message, self.banner.stateIdentifier, self.banner.type, self.banner.show) = ("ERROR", "TOKEN", "error", true)
self.loginState = .failed(stateIdentifier: "TOKEN", errorMessage: "ERROR")
completion(false)
}
case .failure(let error):
(self.banner.message, self.banner.stateIdentifier, self.banner.type, self.banner.show) = (error.errorMessage, error.statusCode, "error", true)
self.loginState = .failed(stateIdentifier: error.statusCode, errorMessage: error.errorMessage)
completion(false)
}
} receiveValue: { response in
self.loginResult = response
}
self.cancellables.insert(cancellable)
}
API服务如下:
func request<T: Decodable>(ofType type: T.Type, from endpoint: APIRequest, body: String) -> AnyPublisher<T, Error> {
var request = endpoint.urlRequest
request.httpMethod = endpoint.method
if endpoint.authenticated == true {
request.setValue(KeychainStorage.getAPIToken()?.token, forHTTPHeaderField: "token")
}
if !body.isEmpty {
let finalBody = body.data(using: .utf8)
request.httpBody = finalBody
}
return URLSession
.shared
.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.mapError { _ in Error.unknown}
.flatMap { data, response -> AnyPublisher<T, Error> in
guard let response = response as? HTTPURLResponse else {
return Fail(error: Error.unknown).eraseToAnyPublisher()
}
let jsonDecoder = JSONDecoder()
if response.statusCode == 200 {
return Just(data)
.decode(type: T.self, decoder: jsonDecoder)
.mapError { _ in Error.decodingError }
.eraseToAnyPublisher()
}
else if response.statusCode == 403 {
let credentials = KeychainStorage.getCredentials()
let signinModel: SigninViewModel = SigninViewModel()
signinModel.credentials = credentials!
signinModel.login() { success in
if success == true {
-------------------> // MAKE THE API CALL AGAIN AND THUS RETURN SOME VALUE
}
else {
-------------------> // RETURN AN ERROR
}
}
}
else if response.statusCode == 429 {
return Fail(error: Error.errorCode(statusCode: response.statusCode, errorMessage: "Oeps! Je hebt teveel verzoeken gedaan, wacht een minuutje")).eraseToAnyPublisher()
}
else {
do {
let errorMessage = try jsonDecoder.decode(APIErrorMessage.self, from: data)
return Fail(error: Error.errorCode(statusCode: response.statusCode, errorMessage: errorMessage.error ?? "Er is iets foutgegaan")).eraseToAnyPublisher()
}
catch {
return Fail(error: Error.decodingError).eraseToAnyPublisher()
}
}
}
.eraseToAnyPublisher()
}
【问题讨论】:
标签: swift asynchronous combine completionhandler