Genere su propio código de error en swift 3

88

Lo que estoy tratando de lograr es realizar una URLSessionsolicitud en swift 3. Estoy realizando esta acción en una función separada (para no escribir el código por separado para GET y POST) y devolviendo URLSessionDataTasky manejando el éxito y el fracaso en los cierres. Algo así como esto

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!)
               }
          }
     }
}

No deseo manejar la condición de error en esta función y deseo generar un error usando el código de respuesta y devolver este Error para manejarlo desde donde se llame a esta función. ¿Alguien puede decirme cómo hacer esto? ¿O no es esta la forma "rápida" de manejar estas situaciones?

Rikh
fuente
Intente usar en NSErrorlugar de Erroren la declaración ( var errorTemp = NSError(...))
Luca D'Alberti
Eso resuelve el problema, pero pensé que Swift 3 no desea continuar usando NS.
Rikh
Lo hace en el desarrollo de iOS. Para el desarrollo puro de Swift, debe crear su propia instancia de error conforme al Errorprotocolo
Luca D'Alberti
@ LucaD'Alberti Bueno, su solución resolvió el problema, ¡no dude en agregarla como respuesta para que pueda aceptarla!
Rikh

Respuestas:

72

Puede crear un protocolo, conforme al LocalizedErrorprotocolo Swift , con estos valores:

protocol OurErrorProtocol: LocalizedError {

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

Esto nos permite crear errores concretos como este:

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
    }
}
Harry Bloom
fuente
3
a) No es necesario crear OurErrorProtocol, solo haga que CustomError implemente Error directamente. b) esto no funciona (al menos en Swift 3: nunca se llama a localizedDescription y aparece "No se pudo completar la operación"). En su lugar, debe implementar LocalizedError; mira mi respuesta.
prewett
@prewett Me acabo de dar cuenta, ¡pero tienes razón! La implementación del campo errorDescription en LocalizedError de hecho establece el mensaje en lugar de usar mi método como se describe anteriormente. Sin embargo, sigo manteniendo el contenedor "OurErrorProtocol", ya que también necesito el campo localizedTitle. ¡Gracias por señalar eso!
Harry Bloom
106

En su caso, el error es que está intentando generar una Errorinstancia. Erroren Swift 3 es un protocolo que se puede utilizar para definir un error personalizado. Esta característica es especialmente para que las aplicaciones Swift puras se ejecuten en diferentes sistemas operativos.

En el desarrollo de iOS, la NSErrorclase todavía está disponible y se ajusta al Errorprotocolo.

Entonces, si su propósito es solo propagar este código de error, puede reemplazar fácilmente

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

con

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

De lo contrario, comprobar la Sandeep Bhandari 's respuesta con respecto a la forma de crear un tipo de error personalizado

Luca D'Alberti
fuente
15
Acabo de obtener el error: Error cannot be created because it has no accessible initializers.
Supertecnoboff
@AbhishekThapliyal, ¿podría elaborar un poco más su comentario? No puedo entender lo que quieres decir.
Luca D'Alberti
2
@ LucaD'Alberti, como en Swift 4, muestra Error no se puede crear porque no tiene inicializadores accesibles, mientras crea Objeto de error.
Maheep
1
@Maheep, lo que sugiero en mi respuesta es no usar Error, pero NSError. Por supuesto, usar Errorarroja un error.
Luca D'Alberti
El error es el protocolo. No se puede instanciar directamente.
slobodans
52

Puede crear enumeraciones para lidiar con errores :)

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

y luego crea un método dentro de enum para recibir el código de respuesta http y devolver el error correspondiente a cambio :)

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
        }
    }

Finalmente actualice su bloque de falla para aceptar un solo parámetro de tipo RikhError :)

Tengo un tutorial detallado sobre cómo reestructurar el modelo de red tradicional orientado a objetos basado en Objective-C al modelo moderno orientado a protocolos usando Swift3 aquí https://learnwithmehere.blogspot.in Eche un vistazo :)

Espero eso ayude :)

Sandeep Bhandari
fuente
Ahh, pero ¿esto no tendrá que hacerme manejar manualmente todos los casos? ¿Eso es escribir los códigos de error?
Rikh
Sí, tienes que: D Pero al mismo tiempo puedes tomar varias acciones específicas para cada estado de error :) ahora tienes un control fino sobre el modelo de error si en caso de que no quieras hacerlo puedes usar el caso 400 ... 404 {...} manejar solo casos genéricos :)
Sandeep Bhandari
¡Ahh sí! Gracias
Rikh
Suponiendo que no es necesario que varios códigos http apunten al mismo caso, debería poder hacer la enumeración RikhError: Int, Error {case invalidRequest = 400} y luego crearlo RikhError (rawValue: httpCode)
Brian F Leighty
48

Debe utilizar el objeto NSError.

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

Luego lanza NSError al objeto Error

Ahmed Lotfy
fuente
27

Detalles

  • Versión de Xcode 10.2.1 (10E1001)
  • Rápido 5

Solución de errores de organización en una aplicación

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
        }
    }
}

Uso

//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)
}
Vasily Bodnarchuk
fuente
16

Implementar 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
    }
}

Tenga en cuenta que simplemente implementar Error, por ejemplo, como se describe en una de las respuestas, fallará (al menos en Swift 3), y llamar a localizedDescription dará como resultado la cadena "La operación no se pudo completar. (.StringError error 1.) "

prehumedecido
fuente
¿Debería ser mMsg = msg?
Brett
1
Vaya, cierto. Cambié "msg" a "descripción", que con suerte es un poco más claro que el original.
prewett
4
Puede reducir eso a struct StringError : LocalizedError { public let errorDescription: String? }, y simplemente utilícelo comoStringError(errorDescription: "some message")
Koen.
7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

crear un objeto NSError y convertirlo en Error, mostrarlo en cualquier lugar

private func showLoginError(_ error: Error?) {
    if let errorObj = error {
        UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
    }
}
Suraj K Thomas
fuente
4

Sigo pensando que la respuesta de Harry es la más simple y completa, pero si necesitas algo aún más simple, usa:

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 }
}

Y utilícelo o pruébelo así:

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

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}
Reimond Hill
fuente
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
fuente
1

Sé que ya está satisfecho con una respuesta, pero si está interesado en conocer el enfoque correcto, esto podría ser útil para usted. Preferiría no mezclar el código de error de respuesta http con el código de error en el objeto de error (¿confundido? Continúe leyendo un poco ...).

Los códigos de respuesta http son códigos de error estándar sobre una respuesta http que define situaciones genéricas cuando se recibe una respuesta y varía de 1xx a 5xx (por ejemplo, 200 OK, 408 Request timed out, 504 Gateway timeout etc - http://www.restapitutorial.com/ httpstatuscodes.html )

El código de error en un objeto NSError proporciona una identificación muy específica del tipo de error que describe el objeto para un dominio particular de aplicación / producto / software. Por ejemplo, su aplicación puede usar 1000 para "Lo sentimos, no puede actualizar este registro más de una vez al día" o decir 1001 para "Necesita un rol de administrador para acceder a este recurso" ... que son específicos de su dominio / aplicación. lógica.

Para una aplicación muy pequeña, a veces estos dos conceptos se combinan. Pero como puede ver, son completamente diferentes y muy importantes y útiles para diseñar y trabajar con software grande.

Entonces, puede haber dos técnicas para manejar el código de una mejor manera:

1. La devolución de llamada de finalización realizará todas las comprobaciones

completionHandler(data, httpResponse, responseError) 

2. Su método decide la situación de éxito y error y luego invoca la devolución de llamada correspondiente

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

Feliz codificación :)

Tushar
fuente
Entonces, básicamente, lo que está tratando de decir es pasar el parámetro "datos" en caso de que haya una cadena específica que se muestre en caso de un código de error específico devuelto por el servidor. (Lo siento, ¡puedo ser un poco lento a veces!)
Rikh