#ios #swift #nsurlsession #combine #urlsession
#iOS #swift #nsurlsession #объединить #urlsession
Вопрос:
API, к которому я обращаюсь, может возвращать JSON, содержащий сообщение об ошибке.
Как я могу сказать Combine
, чтобы попытаться декодировать эту пользовательскую ошибку, если я ожидаю, что другой Decodable
объект будет возвращен при успешном запросе?
В настоящее время мой код выглядит следующим образом:
private var cancellable: AnyCancellable?
internal func perform<T>(request: URLRequest, completion: @escaping (Result<T, Error>) -> Void) where T: Decodable {
cancellable = session.dataTaskPublisher(for: request)
.tryMap { output in
guard let response = output.response as? HTTPURLResponse, response.statusCode == 200 else {
throw HTTPError.statusCode
}
return output.data
}
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
.sink(receiveCompletion: { _completion in
guard case .failure(let error) = _completion else {
return
}
completion(.failure(error))
}, receiveValue: { value in
completion(.success(value))
})
}
С URLSession
помощью я бы сделал что-то вроде этого:
URLSession.shared.dataTask(with: request) { data, response, error in
// Check for any connection errors
if let error = error {
completion(.failure(error))
return
}
// Read data
guard let data = data, !data.isEmpty else {
completion(.failure(SPTError.noDataReceivedError))
return
}
// Check response's status code, if it's anything other than 200 (OK), try to decode SPTError from the data.
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
let sptError = (try? JSONDecoder().decode(SPTError.self, from: data)) ?? SPTError.badRequest
completion(.failure(sptError))
return
}
// Decode requested objects
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(.success(object))
} catch {
print(completion(.failure(error)))
}
}.resume()
SPTError
это просто структура, содержащая код и сообщение, она соответствует Codable
Комментарии:
1. И в чем проблема с вашим текущим кодом?
2. Он только пытается декодировать
T
объект, если ответ отличается от200
кода, он выдает общую ошибку, и я хотел бы декодировать данные какSPTError
Ответ №1:
Когда у вас есть условное ветвление, вы можете использовать .flatMap
, чтобы определить, какого издателя возвращать, на основе любых условий, которые вы проверяете.
FlatMap
должен соответствовать типу сбоя восходящего потока и возвращенного издателя, поэтому .mapError
сначала нужно перейти к общему Error
. И поскольку другая ветвь — это другая цепочка издателей, введите «стереть их все», чтобы AnyPublisher
:
URLSession.shared.dataTaskPublisher(for: url)
.mapError { $0 as Error }
.flatMap() { output -> AnyPublisher<T, Error> in
if output.data.isEmpty {
return Fail(error: SPTError.noDataReceivedError).eraseToAnyPublisher()
}
guard let httpResponse = output.response as? HTTPURLResponse else {
return Fail(error: HTTPError.statusCode).eraseToAnyPublisher()
}
if httpResponse.statusCode == 200 {
return Just(output.data)
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
} else {
return Just(output.data)
.decode(type: SPTError.self, decoder: JSONDecoder())
.flatMap { Fail(error: $0) }
.eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()