Manejo de errores en Swift-Language

No leí demasiado a Swift, pero una cosa que noté es que no hay excepciones. Entonces, ¿cómo hacen el manejo de errores en Swift? ¿Alguien ha encontrado algo relacionado con el manejo de errores?

Swift 2 y 3

Las cosas han cambiado un poco en Swift 2, ya que hay un nuevo mecanismo de manejo de errores, que es algo más similar a las excepciones pero diferente en los detalles.

1. Indicación de posibilidad de error

Si función / método quiere indicar que puede arrojar un error, debe contener palabras clave como esta

 func summonDefaultDragon() throws -> Dragon 

Nota: no hay especificaciones para el tipo de error que la función realmente puede arrojar. Esta statement simplemente establece que la función puede lanzar una instancia de cualquier tipo que implemente ErrorType o que no esté lanzando en absoluto.

2. Función de invocación que puede arrojar errores

Para invocar la función necesita usar la palabra clave try, como esta

 try summonDefaultDragon() 

esta línea normalmente debería estar presente bloque de captura dope como este

 do { let dragon = try summonDefaultDragon() } catch DragonError.dragonIsMissing { // Some specific-case error-handling } catch DragonError.notEnoughMana(let manaRequired) { // Other specific-case error-handlng } catch { // Catch all error-handling } 

Nota: catch clause usa todas las potentes funciones de la coincidencia de patrones de Swift para que seas muy flexible aquí.

Puede decidir propagar el error si está llamando a una función de lanzamiento desde una función que está marcada con la palabra clave throws :

 func fulfill(quest: Quest) throws { let dragon = try summonDefaultDragon() quest.ride(dragon) } 

Alternativamente, ¿puede llamar a la función de lanzamiento usando try? :

 let dragonOrNil = try? summonDefaultDragon() 

De esta forma, obtienes el valor de retorno o nulo, si se produce algún error. Usando esta forma no obtienes el objeto de error.

Lo que significa que también puedes combinar try? con declaraciones útiles como:

 if let dragon = try? summonDefaultDragon() 

o

 guard let dragon = try? summonDefaultDragon() else { ... } 

Finalmente, puede decidir que sabe que el error no ocurrirá en realidad (p. Ej., Porque ya ha verificado que son requisitos previos) y ¡use try! palabra clave:

 let dragon = try! summonDefaultDragon() 

Si la función arroja un error, obtendrá un error de tiempo de ejecución en su aplicación y la aplicación finalizará.

3. Lanzar un error

Para lanzar un error, utiliza throw keyword como este

 throw DragonError.dragonIsMissing 

Puede lanzar cualquier cosa que se ajuste al protocolo ErrorType . Para empezar, NSError cumple con este protocolo, pero probablemente le gustaría ir con ErrorType basado en enum que le permite agrupar múltiples errores relacionados, potencialmente con datos adicionales, como este

 enum DragonError: ErrorType { case dragonIsMissing case notEnoughMana(requiredMana: Int) ... } 

Las principales diferencias entre el nuevo mecanismo de error Swift 2 y 3 y las excepciones de estilo Java / C # / C ++ son las siguientes:

  • La syntax es un poco diferente: do-catch + try + defer vs syntax tradicional try-catch-finally .
  • El manejo de excepciones usualmente tiene un tiempo de ejecución mucho más alto en la ruta de excepción que en la ruta de éxito. Este no es el caso con los errores de Swift 2.0, donde la ruta de éxito y la ruta de error cuestan aproximadamente lo mismo.
  • Se debe declarar todo el código de lanzamiento de error, mientras que las excepciones pueden haber sido arrojadas desde cualquier lugar. Todos los errores son “excepciones comprobadas” en la nomenclatura de Java. Sin embargo, a diferencia de Java, no especifica los errores potencialmente arrojados.
  • Las excepciones Swift no son compatibles con las excepciones ObjC. Su bloque do-catch no atrapará ninguna excepción NSException, y viceversa, para eso debe usar ObjC.
  • Las excepciones NSError son compatibles con las convenciones del método Cocoa NSError de devolver false (para funciones de retorno de Bool ) o nil (para AnyObject retorno de AnyObject ) y pasar NSErrorPointer con detalles de error.

Como azúcar sintáctico adicional para facilitar el manejo de errores, hay dos conceptos más

  • acciones diferidas (utilizando la palabra clave defer ) que le permiten lograr el mismo efecto que finalmente bloquea en Java / C # / etc
  • statement de guardia (usando la palabra reservada guard ) que le permite escribir un código menos if / else que en el código de verificación / señalización de error normal.

Swift 1

Errores de tiempo de ejecución:

Como Leandros sugiere para manejar errores de tiempo de ejecución (como problemas de conectividad de red, análisis de datos, apertura de archivos, etc.) debe usar NSError como lo hizo en ObjC, porque Foundation, AppKit, UIKit, etc. informan sus errores de esta manera. Entonces, es más cosa de framework que de lenguaje.

Otro patrón frecuente que se está utilizando son los bloques de éxito / falla del separador como en AFNetworking:

 var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets")) sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad, success: { (NSURLSessionDataTask) -> Void in println("Success") }, failure:{ (NSURLSessionDataTask, NSError) -> Void in println("Failure") }) 

Aún así, el bloque de fallas frecuentemente recibió la instancia de NSError , describiendo el error.

Errores del progtwigdor

Para errores de progtwigdor (como acceso fuera de límites del elemento de matriz, argumentos inválidos pasados ​​a una llamada de función, etc.) usó excepciones en ObjC. El lenguaje Swift no parece tener ningún soporte de idioma para excepciones (como la palabra clave throw , catch , etc.). Sin embargo, como la documentación sugiere que se está ejecutando en el mismo tiempo de ejecución que ObjC, y por lo tanto todavía puede lanzar NSExceptions esta manera:

 NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() 

Simplemente no puedes atraparlos en Swift puro, aunque puedes optar por capturar excepciones en código ObjC.

La pregunta es si debe lanzar excepciones para los errores del progtwigdor, o más bien utilizar las afirmaciones como sugiere Apple en la guía de idiomas.

Actualización 9 de junio de 2015 – Muy importante

Swift 2.0 viene con palabras clave try , throw y catch y la más emocionante es:

Swift traduce automáticamente los métodos de Objective-C que producen errores en métodos que arrojan un error de acuerdo con la funcionalidad de manejo de errores nativos de Swift.

Nota: Los métodos que consumen errores, como los métodos de delegado o los métodos que toman un controlador de finalización con un argumento de objeto NSError, no se convierten en métodos que se lanzan al ser importados por Swift.

Extracto de: Apple Inc. “Uso de Swift con Cocoa y Objective-C (Swift 2 Prerelease).” IBooks.

Ejemplo: (del libro)

 NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"]; NSError *error = nil; BOOL success = [fileManager removeItemAtURL:URL error:&error]; if (!success && error){ NSLog(@"Error: %@", error.domain); } 

El equivalente en swift será:

 let fileManager = NSFileManager.defaultManager() let URL = NSURL.fileURLWithPath("path/to/file") do { try fileManager.removeItemAtURL(URL) } catch let error as NSError { print ("Error: \(error.domain)") } 

Lanzar un error:

 *errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil] 

Se propagará automáticamente a la persona que llama:

 throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil) 

De los libros de Apple, The Swift Programming Language, parece que los errores deberían manejarse usando enum.

Aquí hay un ejemplo del libro.

 enum ServerResponse { case Result(String, String) case Error(String) } let success = ServerResponse.Result("6:00 am", "8:09 pm") let failure = ServerResponse.Error("Out of cheese.") switch success { case let .Result(sunrise, sunset): let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)." case let .Error(error): let serverResponse = "Failure... \(error)" } 

De: Apple Inc. “The Swift Programming Language.” IBooks. https://itun.es/br/jEUH0.l

Actualizar

De los libros de noticias de Apple, “Uso de Swift con Cocoa y Objective-C”. Las excepciones en tiempo de ejecución no ocurren usando idiomas rápidos, por eso no tiene try-catch. En su lugar, utiliza el encadenamiento opcional .

Aquí hay un tramo del libro:

Por ejemplo, en la siguiente lista de códigos, la primera y la segunda líneas no se ejecutan porque la propiedad de longitud y el método characterAtIndex: no existen en un objeto NSDate. La constante myLength se infiere como un Int opcional, y se establece en nil. También puede usar una instrucción if-let para desplegar condicionalmente el resultado de un método al que el objeto puede no responder, como se muestra en la línea tres

 let myLength = myObject.length? let myChar = myObject.characterAtIndex?(5) if let fifthCharacter = myObject.characterAtIndex(5) { println("Found \(fifthCharacter) at index 5") } 

Extracto de: Apple Inc. “Uso de Swift con Cocoa y Objective-C.” IBooks. https://itun.es/br/1u3-0.l


Y los libros también lo alientan a usar el patrón de error de cocoa de Objective-C (NSError Object)

Los informes de errores en Swift siguen el mismo patrón que en Objective-C, con el beneficio adicional de ofrecer valores de retorno opcionales. En el caso más simple, devuelve un valor Bool de la función para indicar si tuvo éxito o no. Cuando necesite informar el motivo del error, puede agregar a la función un parámetro NSError out de tipo NSErrorPointer. Este tipo es más o menos equivalente al NSError de Objective-C **, con seguridad de memoria adicional y tipeo opcional. Puede usar el prefijo y el operador para pasar una referencia a un tipo NSError opcional como un objeto NSErrorPointer, como se muestra en el siguiente listado de códigos.

 var writeError : NSError? let written = myString.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: &writeError) if !written { if let error = writeError { println("write failure: \(error.localizedDescription)") } } 

Extracto de: Apple Inc. “Uso de Swift con Cocoa y Objective-C.” IBooks. https://itun.es/br/1u3-0.l

No hay excepciones en Swift, similar al enfoque de Objective-C.

En desarrollo, puede usar assert para detectar los errores que puedan aparecer y deben corregirse antes de pasar a producción.

El enfoque clásico de NSError no se altera, envía un NSErrorPointer , que se llena.

Breve ejemplo:

 var error: NSError? var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error) if let error = error { println("An error occurred \(error)") } else { println("Contents: \(contents)") } 

El ‘Swift Way’ recomendado es:

 func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)! return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error) } var writeError: NSError? let written = write("~/Error1")(error: &writeError) if !written { println("write failure 1: \(writeError!.localizedDescription)") // assert(false) // Terminate program } 

Sin embargo, prefiero probar / atrapar ya que me parece más fácil de seguir porque mueve el manejo del error a un bloque separado al final, esta disposición a veces se llama “Ruta dorada”. Suerte que puedes hacer esto con cierres:

 TryBool { write("~/Error2")(error: $0) // The code to try }.catch { println("write failure 2: \($0!.localizedDescription)") // Report failure // assert(false) // Terminate program } 

También es fácil agregar una función de rebash:

 TryBool { write("~/Error3")(error: $0) // The code to try }.retry { println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)") return write("~/Error3r") // The code to retry }.catch { println("write failure 3 catch: \($0!.localizedDescription)") // Report failure // assert(false) // Terminate program } 

La lista de TryBool es:

 class TryBool { typealias Tryee = NSErrorPointer -> Bool typealias Catchee = NSError? -> () typealias Retryee = (NSError?, UInt) -> Tryee private var tryee: Tryee private var retries: UInt = 0 private var retryee: Retryee? init(tryee: Tryee) { self.tryee = tryee } func retry(retries: UInt, retryee: Retryee) -> Self { self.retries = retries self.retryee = retryee return self } func retry(retryee: Retryee) -> Self { return self.retry(1, retryee) } func retry(retries: UInt) -> Self { // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil self.retries = retries retryee = nil return self } func retry() -> Self { return retry(1) } func catch(catchee: Catchee) { var error: NSError? for numRetries in 0...retries { // First try is retry 0 error = nil let result = tryee(&error) if result { return } else if numRetries != retries { if let r = retryee { tryee = r(error, numRetries) } } } catchee(error) } } 

Puede escribir una clase similar para probar un valor devuelto opcional en lugar del valor de Bool:

 class TryOptional { typealias Tryee = NSErrorPointer -> T? typealias Catchee = NSError? -> T typealias Retryee = (NSError?, UInt) -> Tryee private var tryee: Tryee private var retries: UInt = 0 private var retryee: Retryee? init(tryee: Tryee) { self.tryee = tryee } func retry(retries: UInt, retryee: Retryee) -> Self { self.retries = retries self.retryee = retryee return self } func retry(retryee: Retryee) -> Self { return retry(1, retryee) } func retry(retries: UInt) -> Self { // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil self.retries = retries retryee = nil return self } func retry() -> Self { return retry(1) } func catch(catchee: Catchee) -> T { var error: NSError? for numRetries in 0...retries { error = nil let result = tryee(&error) if let r = result { return r } else if numRetries != retries { if let r = retryee { tryee = r(error, numRetries) } } } return catchee(error) } } 

La versión TryOptional impone un tipo de devolución no opcional que facilita la progtwigción posterior, por ejemplo, ‘Swift Way:

 struct FailableInitializer { init?(_ id: Int, error: NSErrorPointer) { // Always fails in example if error != nil { error.memory = NSError(domain: "", code: id, userInfo: [:]) } return nil } private init() { // Empty in example } static let fallback = FailableInitializer() } func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry return FailableInitializer(id, error: error) } var failError: NSError? var failure1Temp = failableInitializer(1)(error: &failError) if failure1Temp == nil { println("failableInitializer failure code: \(failError!.code)") failure1Temp = FailableInitializer.fallback } let failure1 = failure1Temp! // Unwrap 

Usando TryOptional:

 let failure2 = TryOptional { failableInitializer(2)(error: $0) }.catch { println("failableInitializer failure code: \($0!.code)") return FailableInitializer.fallback } let failure3 = TryOptional { failableInitializer(3)(error: $0) }.retry { println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)") return failableInitializer(31) }.catch { println("failableInitializer failure code: \($0!.code)") return FailableInitializer.fallback } 

Tenga en cuenta que se desenrolla automáticamente.

Editar: Aunque esta respuesta funciona, es poco más que Objective-C transliterada en Swift. Se ha vuelto obsoleto por los cambios en Swift 2.0. La respuesta de Guilherme Torres Castro arriba es una muy buena introducción a la forma preferida de manejar errores en Swift. VOS

Me tomó un tiempo descifrarlo, pero creo que lo he sospechado. Aunque parece feo. Nada más que una fina capa sobre la versión Objective-C.

Llamar a una función con un parámetro NSError …

 var fooError : NSError ? = nil let someObject = foo(aParam, error:&fooError) // Check something was returned and look for an error if it wasn't. if !someObject { if let error = fooError { // Handle error NSLog("This happened: \(error.localizedDescription)") } } else { // Handle success }` 

Escribir la función que toma un parámetro de error …

 func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject { // Do stuff... if somethingBadHasHappened { if error { error.memory = NSError(domain: domain, code: code, userInfo: [:]) } return nil } // Do more stuff... } 

Contenedor básico alrededor del objective C que le brinda la característica de capturar el try. https://github.com/williamFalcon/SwiftTryCatch

Use como:

 SwiftTryCatch.try({ () -> Void in //try something }, catch: { (error) -> Void in //handle error }, finally: { () -> Void in //close resources }) 

Esta es una respuesta de actualización para swift 2.0. Estoy esperando el modelo de gestión de errores rico en características como en Java. Finalmente, anunciaron las buenas noticias. aquí

Modelo de manejo de errores: el nuevo modelo de manejo de errores en Swift 2.0 se sentirá instantáneamente como algo natural, con palabras clave try, throw y catch familiares. Lo mejor de todo fue que fue diseñado para funcionar perfectamente con Apple SDKs y NSError. De hecho, NSError se ajusta a un ErrorType de Swift. Definitivamente querrá ver la sesión WWDC en Novedades en Swift para saber más al respecto.

p.ej :

 func loadData() throws { } func test() { do { try loadData() } catch { print(error) }} 

Como dijo Guilherme Torres Castro, en Swift 2.0, try , catch , do se puede utilizar en la progtwigción.

Por ejemplo, en CoreData fetch data method, en lugar de put &error como parámetro en managedContext.executeFetchRequest(fetchRequest, error: &error) , ahora solo tenemos que usar use managedContext.executeFetchRequest(fetchRequest) y luego manejamos el error con try , catch ( enlace de documento de Apple )

 do { let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject] if let results = fetchedResults{ people = results } } catch { print("Could not fetch") } 

Si ya has descargado el xcode7 Beta. Intente buscar errores de lanzamiento en Documentaciones y Referencia de API y elija el primer resultado que se muestra, le da una idea básica de lo que se puede hacer para esta nueva syntax. Sin embargo, la documentación completa aún no se publica para muchas API.

Se pueden encontrar técnicas de manipulación de errores más sofisticadas en

Qué hay de nuevo en Swift (2015 Session 106 28m30s)

El manejo de errores es una nueva característica de Swift 2.0. Utiliza las palabras clave try , throw y catch .

Vea el anuncio de Apple Swift 2.0 en el blog oficial de Apple Swift

Comenzando con Swift 2, como otros ya han mencionado, el manejo de errores se logra mejor mediante el uso de do / try / catch y las enumeraciones de ErrorType. Esto funciona bastante bien para los métodos sincrónicos, pero se requiere un poco de astucia para el manejo asíncrono de errores.

Este artículo tiene un gran enfoque para este problema:

https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/

Para resumir:

 // create a typealias used in completion blocks, for cleaner code typealias LoadDataResult = () throws -> NSData // notice the reference to the typealias in the completionHandler func loadData(someID: String, completionHandler: LoadDataResult -> Void) { completionHandler() } 

entonces, la llamada al método anterior sería la siguiente:

 self.loadData("someString", completionHandler: { result: LoadDataResult in do { let data = try result() // success - go ahead and work with the data } catch { // failure - look at the error code and handle accordingly } }) 

Esto parece un poco más limpio que tener un error de callback de HostHandler pasado a la función asíncrona, que era cómo esto se manejaría antes de Swift 2.

Lo que he visto es que, debido a la naturaleza del dispositivo, no desea lanzar un montón de mensajes de manejo de errores crípticos al usuario. Es por eso que la mayoría de las funciones devuelven valores opcionales, luego usted codifica para ignorar el opcional. Si una función vuelve a cero, lo que significa que falló, puede mostrar un mensaje o lo que sea.

Buena y simple lib para manejar la excepción: TryCatchFinally-Swift

Al igual que algunos otros, se ajusta a las características de excepciones del objective C.

Úselo así:

 try { println(" try") }.catch { e in println(" catch") }.finally { println(" finally") }