Ваши ошибки API должны возвращать объекты ошибок.
Например, вы можете сделать:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
}
Где вы кодируете свои ответы в enum
с именем ApiResultCode
, например так:
enum ApiResultCode {
case invalidAppId
case recordNotFound // just an example
...
case unknown(String)
}
extension ApiResultCode {
static func code(for string: String) -> ApiResultCode {
switch string {
case "invalid_app_id": return .invalidAppId
case "record_not_found": return .recordNotFound
...
default: return .unknown(string)
}
}
}
Это перечисление позволяет вам проверять message
коды без засорения вашего кода строковыми литералами.
И если вы анализируете ошибку API, вы можете вернуть ее.Например,
if responseObject.error {
let error = NetworkRequestError.api(responseObject.status, ApiResultCode.code(for: responseObject.message), responseObject.description)
... now pass this `error`, just like any other `Error` object
}
Если вы открыты для более широкой редизайна, я бы лично предложил
- рефакторинг
RequestResult
, чтобы вытащить эти отдельные типы ошибок (вызывающая сторона хочет просто узнать, успешно ли это произошло или не получилось ... если произошел сбой, ему следует посмотреть на объект Error
, чтобы определить причину сбоя); - , но включить в него новое перечисление
Result
значения, а именно Data
при успехе и Error
при неудаче;и - теперь, когда перечисление включает в себя то, что нам нужно в связанных с ним значениях, мы можем полностью исключить
ReturnedData
.
Итак, сначала давайте расширим этот RequestResult
, включив в негоошибка при сбоях и полезная нагрузка при успехе:
public enum Result {
case success(Data)
case failure(Error)
}
На самом деле, современное соглашение состоит в том, чтобы сделать этот универсальный, где вышеприведенное становится Result<Data, Error>
, используя следующее:
public enum Result<T, U> {
case success(T)
case failure(U)
}
(Swift 5 на самом деле включает в себя этот универсальный.)
И тогда я бы расширил ResultError
для обработки как ошибок API, так и любых неизвестных ошибок:
enum NetworkRequestError: Error {
case api(_ status: Int, _ code: ApiResultCode, _ description: String)
case unknown(Data?, URLResponse?)
}
Итак, сделав это,Вы можете изменить request
, чтобы передать обратно Result<Data, Error>
:
static func request(urlString: String, parameters: [String: String], completion: @escaping (Result<Data, Error>) -> ()) {
let request = URLRequest(url: URL(string: urlString)!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let responseData = data, error == nil else {
completion(.failure(error ?? NetworkRequestError.unknown(data, response)))
return
}
completion(.success(responseData))
}
task.resume()
}
И тогда вызывающий абонент сделает:
request(...) { result in
switch result {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
Красота этого Result
общего в том, что онстановится последовательным шаблоном, который вы можете использовать в своем коде.Например, предположим, у вас есть какой-то метод, который собирается проанализировать Foo
объект из Data
, который request
вернул:
func retrieveFoo(completion: @escaping (Result<Foo, Error>) -> Void) {
request(...) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
do {
let responseObject = try JSONDecoder().decode(ResponseObject.self, from: data)
if responseObject.error {
completion(.failure(NetworkRequestError.api(responseObject.status, ApiResultCode.code(for: responseObject.message), responseObject.description)))
return
}
let foo = responseObject.foo
completion(.success(foo))
} catch {
completion(.failure(error))
}
}
}
}
Или, если вы хотите проверить для определенногоОшибка API, например .recordNotFound
:
retrieveFoo { result in
switch result {
case .failure(NetworkRequestError.api(_, .recordNotFound, _)):
// handle specific “record not found” error here
case .failure(let error):
// handle all other errors here
case .success(let foo):
// do something with `foo`
}
}