unreachable try thrown example errors error catch because are ios swift error-handling

ios - thrown - try catch swift 4 example



Manejando errores en Swift (5)

Completaría los errores por su cuenta, pero pasaría el error subyacente como una propiedad en su clase de error ( InnerException ala C #). De esa manera, le brinda a los consumidores una interfaz consistente, pero también proporciona detalles de error de nivel inferior si es necesario. Sin embargo, la razón principal por la que haría esto es para pruebas de unidad. Hace que sea mucho más fácil burlarse de su clase ResourceService y probar las rutas de código para los diversos errores que podrían ocurrir.

No me gusta la idea de devolver una serie de errores, ya que agrega complejidad para el consumidor. En su lugar, proporcionaría una matriz de instancias InnerException . Si son instancias de su propia clase de error, potencialmente tendrían sus propias InnerException con errores subyacentes. Sin embargo, esto probablemente solo tendría sentido si estuviera haciendo sus propias validaciones donde varios errores podrían tener sentido. Su método de descarga probablemente tendrá que rescatarse después del primer error encontrado.

En mi aplicación necesito descargar un archivo JSON de la web. He creado una clase de ResourceService que tiene un método de download como se ve a continuación. Utilizo este servicio en los servicios de "nivel superior" de mi aplicación. Puedes ver que hay varias cosas que pueden salir mal durante la descarga. El servidor podría estar en llamas y no ser capaz de responder con éxito en este momento, podría haber algo mal durante el movimiento del archivo temporal, etc.

Ahora, probablemente no hay mucho que un usuario pueda hacer con esto que no sea intentarlo más tarde. Sin embargo, él / ella probablemente querrá saber que hubo algo incorrecto y que la descarga o el comportamiento de los métodos de "nivel superior" no tuvieron éxito.

Yo como desarrollador estoy confundido en este punto porque no entiendo cómo lidiar con los errores en Swift. Tengo un completionHandler la completionHandler que toma un error si hubo uno, pero no sé qué tipo de error debo devolverle a la persona que llama.

Pensamientos

1) Si paso los objetos de error que recibo de la API de NSFileManager o de la API de NSURLSession, creo que estoy "filtrando" parte de la implementación del método de download a las personas que llaman. ¿Y cómo sabría la persona que llama qué tipo de errores esperar en función del error? Podrían ser ambas cosas.

2) Si se supone que debo detectar y envolver esos errores que podrían ocurrir dentro del método de download , ¿cómo se vería?

3) ¿Cómo trato con múltiples fuentes de error dentro de un método, y cómo se vería el código que llama al método que puede lanzar / devolver objetos NSError?

¿Debería usted, como persona que llama, interceptar los errores que recibe y luego escribir una gran cantidad de código que diferencie los mensajes / acciones tomadas en función del código de error? No obtengo este error en el manejo de las cosas y cómo se vería cuando hay muchas cosas que podrían salir mal en un solo método.

func download(destinationUrl: NSURL, completionHandler: ((error: NSError?) -> Void)) { let request = NSURLRequest(URL: resourceUrl!) let task = downloadSession.downloadTaskWithRequest(request) { (url: NSURL?, response: NSURLResponse?, error: NSError?) in if error == nil { do { try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl) } catch let e { print(e) } } else { } }.resume() }


En primer lugar esta es una gran pregunta. El manejo de errores es una tarea específica que se aplica a una increíble variedad de situaciones con quién sabe qué repercusiones tiene el estado de su aplicación. El problema clave es lo que es significativo para su usuario, la aplicación y usted el desarrollador.

Me gusta ver esto conceptualmente como la forma en que se usa la cadena de respuesta para manejar eventos. Al igual que un evento que atraviesa la cadena de respuesta, un error tiene la posibilidad de aumentar los niveles de abstracción de su aplicación. Dependiendo del error, es posible que desee hacer una serie de cosas relacionadas con el tipo de error. Es posible que los diferentes componentes de su aplicación deban conocer el error, tal vez un error que, según el estado de la aplicación, no requiere ninguna acción.

Usted como desarrollador, en última instancia, sabe dónde afectan los errores a su aplicación y cómo. Entonces, teniendo en cuenta cómo elegimos implementar una solución técnica.

Sugeriría usar Enumerations y Closures para construir mi solución de manejo de errores.

Aquí está un ejemplo artificial de un ENUM. Como puede ver, representa el núcleo de la solución de manejo de errores.

public enum MyAppErrorCode { case NotStartedCode(Int, String) case ResponseOkCode case ServiceInProgressCode(Int, String) case ServiceCancelledCode(Int, String, NSError) func handleCode(errorCode: MyAppErrorCode) { switch(errorCode) { case NotStartedCode(let code, let message): print("code: /(code)") print("message: /(message)") case ResponseOkCode: break case ServiceInProgressCode(let code, let message): print("code: /(code)") print("message: /(message)") case ServiceCancelledCode(let code, let message, let error): print("code: /(code)") print("message: /(message)") print("error: /(error.localizedDescription)") } } }

A continuación, queremos definir nuestro ((error: NSError?) -> Void) que reemplazará ((error: NSError?) -> Void) el cierre que tiene en su método de descarga.

((errorCode: MyAppErrorCode) -> Void)

Nueva función de descarga

func download(destinationUrl: NSURL, completionHandler: ((errorCode: MyAppErrorCode) -> Void)) { let request = NSURLRequest(URL: resourceUrl!) let task = downloadSession.downloadTaskWithRequest(request) { (url: NSURL?, response: NSURLResponse?, error: NSError?) in if error == nil { do { try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl) completionHandler(errorCode: MyAppErrorCode.ResponseOkCode) } catch let e { print(e) completionHandler(errorCode: MyAppErrorCode.MoveItemFailedCode(170, "Text you would like to display to the user..", e)) } } else { completionHandler(errorCode: MyAppErrorCode.DownloadFailedCode(404, "Text you would like to display to the user..")) } }.resume() }

En el cierre que pase, puede llamar a handleCode(errorCode: MyAppErrorCode) o cualquier otra función que haya definido en el ENUM.

Ahora tiene los componentes para definir su propia solución de manejo de errores que es fácil de adaptar a su aplicación y que puede usar para asignar los códigos http y cualquier otro código de error / respuesta de terceros a algo significativo en su aplicación. También puede elegir si es útil dejar que el NSError suba.

EDITAR

De vuelta a nuestros artilugios.

¿Cómo lidiamos con la interacción con nuestros controladores de vista? Podemos elegir tener un mecanismo centralizado como lo tenemos ahora o podríamos manejarlo en el controlador de vista y mantener el alcance local. Para ello, moveríamos la lógica del ENUM al controlador de vista y abordaríamos los requisitos muy específicos de la tarea de nuestro controlador de vista (descarga en este caso), también podría mover el ENUM al alcance del controlador de vista. Logramos la encapsulación, pero terminaremos repitiendo nuestro código en otro lugar del proyecto. De cualquier manera, su controlador de vista tendrá que hacer algo con el código de error / resultado

Un enfoque que prefiero sería darle al controlador de la vista la oportunidad de manejar un comportamiento específico en el controlador de finalización, o luego pasarlo a nuestro ENUM para un comportamiento más general, como enviar una notificación de que la descarga ha finalizado, actualizar el estado de la aplicación o acaba de lanzar un AlertViewController con una sola acción para ''Aceptar''.

Hacemos esto agregando métodos a nuestro controlador de vista a los que se puede pasar MyAppErrorCode ENUM y cualquier variable relacionada (URL, solicitud ...) y agregamos cualquier variable de instancia para realizar un seguimiento de nuestra tarea, es decir, una URL diferente o el número de intentos antes de renunciar a tratar de hacer la descarga.

Aquí hay un posible método para manejar la descarga en el controlador de vista:

func didCompleteDownloadWithResult(resultCode: MyAppErrorCode, request: NSURLRequest, url: NSURL) { switch(resultCode) { case .ResponseOkCode: // Made up method as an example resultCode.postSuccessfulDownloadNotification(url, dictionary: ["request" : request]) case .FailedDownloadCode(let code, let message, let error): if numberOfAttempts = maximumAttempts { // Made up method as an example finishedAttemptingDownload() } else { // Made up method as an example AttemptDownload(numberOfAttempts) } default: break } }


Larga historia corta: si

... y luego escribe un montón de código que diferencia los mensajes / acciones tomadas en función del código de error?

La mayoría de los ejemplos de código dejan al programador solo en cuanto a cómo hacer cualquier manejo de errores, pero para hacerlo correctamente, su código de manejo de errores puede ser más que el código para respuestas exitosas. Especialmente cuando se trata de redes y análisis de json.

En uno de mis últimos proyectos (una gran cantidad de comunicaciones de servidor json con estado) he implementado el siguiente enfoque: Me he preguntado: ¿Cómo debería la aplicación reaccionar ante el usuario en caso de error (y traducirlo para que sea más fácil de usar?) )?

  • ignoralo
  • mostrar un mensaje / alerta (posiblemente solo uno)
  • vuelva a intentarlo por sí mismo (¿con qué frecuencia?)
  • obligar al usuario a comenzar de nuevo
  • asumir (es decir, una respuesta previamente almacenada en caché)

Para lograr esto, he creado una clase ErrorHandler central, que tiene varias enumeraciones para los diferentes tipos de errores (es decir, enum NetworkResponseCode, ServerReturnCode, LocationStatusCode) y una enumeración para los diferentes ErrorDomains:

enum MyErrorDomain : String { // if request data has errors (i.e. json not valid) case NetworkRequestDomain = "NetworkRequest" // if network response has error (i.e. offline or http status code != 200) case NetworkResponseDomain = "NetworkResponse" // server return code in json: value of JSONxxx_JSON_PARAM_xxx_RETURN_CODE case ServerReturnDomain = "ServerReturnCode" // server return code in json: value of JSONxxxStatus_xxx_JSON_PARAM_xxx_STATUS_CODE case ServerStatusDomain = "ServerStatus" // if CLAuthorizationStatus case LocationStatusDomain = "LocationStatus" .... }

Además, existen algunas funciones de ayuda llamadas createError. Estos métodos verifican la condición de error (es decir, los errores de red son diferentes si está desconectado o si la respuesta del servidor es = 200). Son más cortos de lo que cabría esperar.

Y para ponerlo todo junto hay una función que maneja el error.

func handleError(error: NSError, msgType: String, shouldSuppressAlert: Bool = false){ ... }

Este método comenzó con la instrucción de cambio (y ahora necesita un poco de refactorización, por lo que no lo mostraré ya que todavía es uno). En esta declaración se implementan todas las reacciones posibles. Es posible que necesite un tipo de devolución diferente para mantener su estado correctamente en la aplicación.

Lecciones aprendidas:

  • Aunque pensé que había empezado en grande (enumeración diferente, alerta de usuario central), la arquitectura podría haber sido mejor (es decir, varias clases, herencia, ...).
  • Necesitaba hacer un seguimiento de los errores anteriores (ya que algunos son seguimientos) para mostrar solo un mensaje de error al usuario -> estado.
  • Hay buenas razones para ocultar errores.
  • Dentro del mapa errorObj.userInfo, sale de un mensaje de error fácil de usar y un technicalErrorMessage (que se envía a un proveedor de seguimiento).
  • Hemos introducido códigos de error numéricos (el dominio de error tiene el prefijo de una letra) que son coherentes entre el cliente y el servidor. También se muestran al usuario. Esto realmente ha ayudado a rastrear errores.
  • He implementado una función handleSoftwareBug (que es casi lo mismo que handleError pero mucho menos casos). Se usa en muchos bloques else que normalmente no te molestas en escribir (ya que piensas que este estado nunca puede alcanzarse). Sorprendentemente se puede.

    ErrorHandler.sharedInstance.handleSoftwareBug("SW bug? Unknown received error code string was code: /(code)")

Cómo se ve en el código: hay muchas solicitudes de red de back-end similares donde muchos de los códigos se parecen a los siguientes:

func postAllXXX(completionHandler:(JSON!, NSError!) -> Void) -> RegisteringSessionTask { log.function() return postRegistered(jsonDict: self.jsonFactory.allXXX(), outgoingMsgType: JSONClientMessageToServerAllXXX, expectedIncomingUserDataType: JSONServerResponseAllXXX, completionHandler: {(json, error) in if error != nil { log.error("error: /(error.localizedDescription)") ErrorHandler.sharedInstance.handleError(error, msgType: JSONServerResponseAllXXX, shouldSuppressAlert: true) dispatch_async(dispatch_get_main_queue(), { completionHandler(json, error) }) return } // handle request payload var returnList:[XXX] = [] let xxxList = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_YYY].arrayValue ..... dispatch_async(dispatch_get_main_queue(), { completionHandler(json, error) }) }) }

Dentro del código anterior, usted ve que yo llamo un completionHandler y le doy a esta persona que llama la posibilidad de personalizar también el manejo de errores. La mayoría de las veces, esta persona que llama solo maneja el éxito.

Siempre que tuve la necesidad de reintentos y otro manejo no tan común, también lo he hecho en el lado de la persona que llama, es decir,

private func postXXXMessageInternal(completionHandler:(JSON!, NSError!) -> Void) -> NSURLSessionDataTask { log.function() return self.networkquery.postServerJsonEphemeral(url, jsonDict: self.jsonFactory.xxxMessage(), outgoingMsgType: JSONClientMessageToServerXXXMessage, expectedIncomingUserDataType: JSONServerResponseXXXMessage, completionHandler: {(json, error) in if error != nil { self.xxxMessageErrorWaitingCounter++ log.error("error(/(self.xxxMessageErrorWaitingCounter)): /(error.localizedDescription)") if (something || somethingelse) && self.xxxMessageErrorWaitingCounter >= MAX_ERROR_XXX_MESSAGE_WAITING { // reset app because of too many errors xxx.currentState = AppState.yyy ErrorHandler.sharedInstance.genericError(MAX_ERROR_XXX_MESSAGE_WAITING, shouldSuppressAlert: false) dispatch_async(dispatch_get_main_queue(), { completionHandler(json, nil) }) self.xxxMessageErrorWaitingCounter = 0 return } // handle request payload if let msg = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_ZZZ].stringValue { ..... } ..... dispatch_async(dispatch_get_main_queue(), { completionHandler(json, error) }) }) }

Aquí hay otro ejemplo donde el usuario se ve obligado a volver a intentarlo.

// user did not see a price. should have been fetched earlier (something is wrong), cancel any ongoing requests ErrorHandler.sharedInstance.handleSoftwareBug("potentially sw bug (or network to slow?): no payment there? user must retry") if let st = self.sessionTask { st.cancel() self.sessionTask = nil } // tell user ErrorHandler.sharedInstance.genericInfo(MESSAGE_XXX_PRICE_REQUIRED) // send him back xxx.currentState = AppState.zzz return


Para cualquier solicitud, recibe un error o un código de estado http. Error significa: su aplicación nunca logró comunicarse correctamente con el servidor. Código de estado http significa: su aplicación habló con un servidor. Tenga en cuenta que si lleva su iPhone al Starbucks más cercano, "su aplicación habló con un servidor" no significa que "su aplicación habló con el servidor con el que quería hablar". Podría significar que "su aplicación logró comunicarse con el servidor de Starbucks, que le pide que inicie sesión y no tiene idea de cómo hacerlo".

Divido los posibles errores en categorías: "Es un error en mi código". Ahí es donde necesitas arreglar tu código. "Algo salió mal, y el usuario puede hacer algo al respecto". Por ejemplo cuando el WiFi está apagado. "Algo salió mal, tal vez funcione más tarde". Puedes decirle al usuario que intente más tarde. "Algo salió mal, y el usuario no puede hacer nada al respecto". Difícil. "Recibí una respuesta del servidor que esperaba. Tal vez un error, tal vez no, pero algo que sé cómo manejar". Usted lo maneja.

También divido las llamadas en categorías: las que deben ejecutarse de manera invisible en segundo plano y las que se ejecutan como resultado de una acción directa del usuario. Las cosas que se ejecutan de manera invisible en segundo plano no deberían dar mensajes de error. (El sangriento iTunes que me dice que no puede conectarse a la tienda iTunes Store cuando no tenía interés en conectarme a la tienda iTunes Store en primer lugar es un ejemplo terrible de hacerlo mal).

Cuando le muestres cosas al usuario, recuerda que al usuario no le importa. Para el usuario: O funcionó o no funcionó. Si no funcionó, el usuario puede solucionar el problema si es un problema que puede solucionar, puede intentarlo más tarde, o simplemente es una mala suerte. En una aplicación empresarial, puede tener un mensaje "llame a su mesa de ayuda al xxxxxx y dígales yyyyyy".

Y cuando las cosas no funcionan, no moleste al usuario mostrando error tras error tras error. Si envía solicitudes, no le diga al usuario diez veces que el servidor está en llamas.

Hay cosas que simplemente no esperas que salgan mal. Si descarga un archivo y no puede ponerlo donde pertenece, bueno, eso es difícil. No debería suceder. El usuario no puede hacer nada al respecto. (Bueno, tal vez puedan. Si el almacenamiento del dispositivo está lleno, puede avisarle al usuario). Aparte de eso, es la misma categoría que "Algo salió mal y el usuario no puede hacer nada al respecto". Como desarrollador, puede descubrir cuál es la causa y solucionarlo, pero si sucede con una aplicación en las manos del usuario, no hay nada razonable que pueda hacer.

Como todas estas solicitudes deben ser asíncronas, siempre pasará uno o dos bloques de devolución de llamada a la llamada, uno para el éxito y otro para el fracaso. Tengo la mayor parte del manejo de errores en el código de descarga, por lo que cosas como pedirle al usuario que active el WiFi solo ocurren una vez, y las llamadas pueden repetirse automáticamente si el usuario arregla dicha condición de error. La devolución de llamada de error se utiliza principalmente para informar a la aplicación que no obtendrá los datos que deseaba; A veces el hecho de que haya un error es información útil en sí misma.


Para un manejo consistente de errores, creo mis propios errores que representan errores devueltos por la sesión o códigos de estado html interpretados como errores. Además de dos errores adicionales "usuario cancelado" y "no se permite la interacción del usuario" si hubo una IU involucrada y el usuario canceló la operación, o si quería usar alguna interacción del usuario pero no se me permitió. Los dos últimos errores son diferentes: estos errores nunca se informarán al usuario.