Создайте свой собственный код ошибки в Swift 3

88

Я пытаюсь выполнить URLSessionзапрос в быстром 3. Я выполняю это действие в отдельной функции (чтобы не писать код отдельно для GET и POST) и возвращаю URLSessionDataTaskи обрабатываю успех и неудачу при закрытии. Что-то вроде этого-

let task = URLSession.shared.dataTask(with: request) { (data, uRLResponse, responseError) in

     DispatchQueue.main.async {

          var httpResponse = uRLResponse as! HTTPURLResponse

          if responseError != nil && httpResponse.statusCode == 200{

               successHandler(data!)

          }else{

               if(responseError == nil){
                     //Trying to achieve something like below 2 lines
                     //Following line throws an error soo its not possible
                     //var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

                     //failureHandler(errorTemp)

               }else{

                     failureHandler(responseError!)
               }
          }
     }
}

Я не хочу обрабатывать состояние ошибки в этой функции и хочу сгенерировать ошибку, используя код ответа, и вернуть эту ошибку, чтобы обработать ее, откуда бы эта функция ни вызывалась. Кто-нибудь может сказать мне, как это сделать? Или это не «быстрый» способ справляться с такими ситуациями?

Рих
источник
Попробуйте использовать NSErrorвместо Errorв объявлении ( var errorTemp = NSError(...))
Лука Д'Алберти
Это решает проблему, но я думал, что Swift 3 не хочет продолжать использовать NS?
Rikh
Это работает в разработке для iOS. Для разработки на чистом Swift вы должны создать свой собственный экземпляр ошибки, согласовывая Errorпротокол
Лука Д'Алберти,
@ LucaD'Alberti Что ж, ваше решение действительно решило проблему, не стесняйтесь добавлять его в качестве ответа, чтобы я мог его принять!
Rikh

Ответы:

72

Вы можете создать протокол, соответствующий LocalizedErrorпротоколу Swift , со следующими значениями:

protocol OurErrorProtocol: LocalizedError {

    var title: String? { get }
    var code: Int { get }
}

Затем это позволяет нам создавать конкретные ошибки, например:

struct CustomError: OurErrorProtocol {

    var title: String?
    var code: Int
    var errorDescription: String? { return _description }
    var failureReason: String? { return _description }

    private var _description: String

    init(title: String?, description: String, code: Int) {
        self.title = title ?? "Error"
        self._description = description
        self.code = code
    }
}
Гарри Блум
источник
3
a) нет необходимости создавать OurErrorProtocol, просто укажите, что CustomError напрямую реализует Error. б) это не работает (по крайней мере, в Swift 3: localizedDescription никогда не вызывается, и вы получаете сообщение «Операция не может быть завершена»). Вместо этого вам нужно реализовать LocalizedError; смотри мой ответ.
Prewett
@prewett Я только что заметил, но вы правы! Реализация поля errorDescription в LocalizedError фактически устанавливает сообщение, а не использует мой метод, как описано выше. Я все еще сохраняю оболочку OurErrorProtocol, так как мне также нужно поле localizedTitle. Спасибо что подметил это!
Гарри Блум
106

В вашем случае ошибка в том, что вы пытаетесь создать Errorэкземпляр. Errorв Swift 3 - это протокол, который можно использовать для определения пользовательской ошибки. Эта функция особенно важна для приложений на чистом Swift, работающих в разных ОС.

В разработке для iOS NSErrorкласс по-прежнему доступен и соответствует Errorпротоколу.

Итак, если ваша цель - только распространить этот код ошибки, вы можете легко заменить

var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

с участием

var errorTemp = NSError(domain:"", code:httpResponse.statusCode, userInfo:nil)

В противном случае проверьте на SANDEEP Bhandari «S ответ о том , как создать пользовательский тип ошибки

Лука Д'Альберти
источник
15
Я просто получаю ошибку: Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal, не могли бы вы уточнить свой комментарий? Я не понимаю, о чем ты.
Лука Д'Алберти
2
@ LucaD'Alberti, как и в Swift 4, его показ «Ошибка» не может быть создан, поскольку он не имеет доступных инициализаторов при создании объекта ошибки.
Maheep
1
@Maheep в своем ответе я предлагаю не использовать Error, но NSError. Конечно, использование Errorвызывает ошибку.
Лука Д'Алберти
Ошибка - это протокол. Не может быть создан напрямую.
slobodans
52

Вы можете создавать перечисления для устранения ошибок :)

enum RikhError: Error {
    case unknownError
    case connectionError
    case invalidCredentials
    case invalidRequest
    case notFound
    case invalidResponse
    case serverError
    case serverUnavailable
    case timeOut
    case unsuppotedURL
 }

а затем создайте метод внутри enum для получения кода ответа http и возврата соответствующей ошибки :)

static func checkErrorCode(_ errorCode: Int) -> RikhError {
        switch errorCode {
        case 400:
            return .invalidRequest
        case 401:
            return .invalidCredentials
        case 404:
            return .notFound
        //bla bla bla
        default:
            return .unknownError
        }
    }

Наконец обновите свой блок сбоя, чтобы он принял единственный параметр типа RikhError :)

У меня есть подробное руководство о том, как реструктурировать традиционную объектно-ориентированную сетевую модель на основе Objective - C в современную протоколно-ориентированную модель с использованием Swift3 здесь https://learnwithmehere.blogspot.in Посмотрите :)

Надеюсь, это поможет :)

Сандип Бхандари
источник
Ах, но разве это не заставит меня вручную обрабатывать все дела? То есть типа коды ошибок?
Rikh
Да, вы должны: D Но в то же время вы можете выполнять различные действия, специфичные для каждого статуса ошибки :) теперь у вас есть точный контроль над моделью ошибки, если в случае, если вы не хотите этого делать, вы можете использовать случай 400 ... 404 {...} обрабатывать только общие случаи :)
Сандип Бхандари
Ага! Спасибо
Rikh
Предполагая, что несколько кодов http не должны указывать на один и тот же случай, вы должны иметь возможность просто выполнить перечисление RikhError: Int, Error {case invalidRequest = 400}, а затем создать его RikhError (rawValue: httpCode)
Брайан Ф. Лейти,
48

Вы должны использовать объект NSError.

let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invalid access token"])

Затем приведите NSError к объекту ошибки

Ахмед Лотфи
источник
27

Детали

  • Xcode версии 10.2.1 (10E1001)
  • Swift 5

Решение ошибок организации в приложении

import Foundation

enum AppError {
    case network(type: Enums.NetworkError)
    case file(type: Enums.FileError)
    case custom(errorDescription: String?)

    class Enums { }
}

extension AppError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .network(let type): return type.localizedDescription
            case .file(let type): return type.localizedDescription
            case .custom(let errorDescription): return errorDescription
        }
    }
}

// MARK: - Network Errors

extension AppError.Enums {
    enum NetworkError {
        case parsing
        case notFound
        case custom(errorCode: Int?, errorDescription: String?)
    }
}

extension AppError.Enums.NetworkError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .parsing: return "Parsing error"
            case .notFound: return "URL Not Found"
            case .custom(_, let errorDescription): return errorDescription
        }
    }

    var errorCode: Int? {
        switch self {
            case .parsing: return nil
            case .notFound: return 404
            case .custom(let errorCode, _): return errorCode
        }
    }
}

// MARK: - FIle Errors

extension AppError.Enums {
    enum FileError {
        case read(path: String)
        case write(path: String, value: Any)
        case custom(errorDescription: String?)
    }
}

extension AppError.Enums.FileError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .read(let path): return "Could not read file from \"\(path)\""
            case .write(let path, let value): return "Could not write value \"\(value)\" file from \"\(path)\""
            case .custom(let errorDescription): return errorDescription
        }
    }
}

Применение

//let err: Error = NSError(domain:"", code: 401, userInfo: [NSLocalizedDescriptionKey: "Invaild UserName or Password"])
let err: Error = AppError.network(type: .custom(errorCode: 400, errorDescription: "Bad request"))

switch err {
    case is AppError:
        switch err as! AppError {
        case .network(let type): print("Network ERROR: code \(type.errorCode), description: \(type.localizedDescription)")
        case .file(let type):
            switch type {
                case .read: print("FILE Reading ERROR")
                case .write: print("FILE Writing ERROR")
                case .custom: print("FILE ERROR")
            }
        case .custom: print("Custom ERROR")
    }
    default: print(err)
}
Василий Боднарчук
источник
16

Реализуйте LocalizedError:

struct StringError : LocalizedError
{
    var errorDescription: String? { return mMsg }
    var failureReason: String? { return mMsg }
    var recoverySuggestion: String? { return "" }
    var helpAnchor: String? { return "" }

    private var mMsg : String

    init(_ description: String)
    {
        mMsg = description
    }
}

Обратите внимание, что простая реализация Error, например, как описано в одном из ответов, завершится ошибкой (по крайней мере, в Swift 3), а вызов localizedDescription приведет к появлению строки «Операция не может быть завершена. (Ошибка .StringError 1.) "

Prewett
источник
Если это будет mMsg = msg
Бретт
1
Ой, верно. Я изменил "msg" на "description", что, надеюсь, немного яснее моего оригинала.
prewett
4
Вы можете уменьшить это до struct StringError : LocalizedError { public let errorDescription: String? }, а это просто использовать какStringError(errorDescription: "some message")
Koen.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

создать объект NSError и привести его к типу Error, показать где угодно

private func showLoginError(_ error: Error?) {
    if let errorObj = error {
        UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
    }
}
Сурадж К. Томас
источник
4

Я все еще думаю, что ответ Гарри самый простой и законченный, но если вам нужно что-то еще более простое, используйте:

struct AppError {
    let message: String

    init(message: String) {
        self.message = message
    }
}

extension AppError: LocalizedError {
    var errorDescription: String? { return message }
//    var failureReason: String? { get }
//    var recoverySuggestion: String? { get }
//    var helpAnchor: String? { get }
}

И используйте или протестируйте это так:

printError(error: AppError(message: "My App Error!!!"))

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}
Реймонд Хилл
источник
2
protocol CustomError : Error {

    var localizedTitle: String
    var localizedDescription: String

}

enum RequestError : Int, CustomError {

    case badRequest         = 400
    case loginFailed        = 401
    case userDisabled       = 403
    case notFound           = 404
    case methodNotAllowed   = 405
    case serverError        = 500
    case noConnection       = -1009
    case timeOutError       = -1001

}

func anything(errorCode: Int) -> CustomError? {

      return RequestError(rawValue: errorCode)
}
Daniel.scheibe
источник
1

Я знаю, что вы уже удовлетворены ответом, но если вам интересно узнать правильный подход, это может быть вам полезно. Я бы предпочел не смешивать код ошибки http-ответа с кодом ошибки в объекте ошибки (запутались? Продолжите читать немного ...).

Коды HTTP-ответа - это стандартные коды ошибок HTTP-ответа, определяющие общие ситуации получения ответа, и варьируются от 1xx до 5xx (например, 200 OK, 408 Request timed out, 504 Gateway timeout и т. Д. - http://www.restapitutorial.com/ httpstatuscodes.html )

Код ошибки в объекте NSError обеспечивает очень конкретную идентификацию типа ошибки, которую объект описывает для конкретной области приложения / продукта / программного обеспечения. Например, ваше приложение может использовать 1000 для «Извините, вы не можете обновлять эту запись более одного раза в день» или скажите 1001 для «Вам нужна роль менеджера для доступа к этому ресурсу» ... которые относятся к вашему домену / приложению. логика.

Для очень небольшого приложения иногда эти две концепции объединяются. Но они совершенно разные, как видите, и очень важны и полезны для проектирования и работы с большим программным обеспечением.

Итак, есть два метода для лучшей обработки кода:

1. Обратный вызов завершения выполнит все проверки.

completionHandler(data, httpResponse, responseError) 

2. Ваш метод определяет успешную или ошибочную ситуацию, а затем вызывает соответствующий обратный вызов.

if nil == responseError { 
   successCallback(data)
} else {
   failureCallback(data, responseError) // failure can have data also for standard REST request/response APIs
}

Удачного кодирования :)

Тушар
источник
Итак, в основном то, что вы пытаетесь сказать, - это передать параметр «данные» в случае, если есть какая-то конкретная строка, которая будет отображаться в случае определенного кода ошибки, возвращаемого с сервера? (Извините, временами я могу быть немного медленным!)
Rikh