mac language for docs descargar blog swift swift2 cocoa-touch exception foundation nsexception

language - Capturando NSException en Swift



swift language (5)

El siguiente código en Swift provoca la excepción NSInvalidArgumentException:

task = NSTask() task.launchPath = "/SomeWrongPath" task.launch()

¿Cómo puedo atrapar la excepción? Según tengo entendido, try / catch en Swift es para errores lanzados dentro de Swift, no para NSExceptions generadas a partir de objetos como NSTask (que supongo que está escrito en ObjC). Soy nuevo en Swift, así que tal vez me estoy perdiendo algo obvio ...

Editar : aquí hay un radar para el error (específicamente para NSTask): openradar.appspot.com/22837476


Aquí hay un código que convierte NSExceptions en errores de Swift 2.

Ahora puedes usar

do { try ObjC.catchException { /* calls that might throw an NSException */ } } catch { print("An error ocurred: /(error)") }

ObjC.h:

#import <Foundation/Foundation.h> @interface ObjC : NSObject + (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; @end

ObjC.m

#import "ObjC.h" @implementation ObjC + (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { @try { tryBlock(); return YES; } @catch (NSException *exception) { *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; return NO; } } @end

No olvide agregar esto a su "* -Bridging-Header.h":

#import "ObjC.h"


Como se especifica en la documentation , esta es una forma fácil y ligera de hacerlo:

do { try fileManager.moveItem(at: fromURL, to: toURL) } catch let error as NSError { print("Error: /(error.domain)") }


Como se señaló en los comentarios, que esta API arroja excepciones para condiciones de falla recuperables de otro modo es un error. Archívelo y solicite una alternativa basada en NSError. Principalmente, el estado actual de las cosas es un anacronismo, ya que NSTask data de antes de que Apple estandarizara que las excepciones sean solo para errores de programador.

Mientras tanto, si bien puede usar uno de los mecanismos de otras respuestas para detectar excepciones en ObjC y pasarlas a Swift, tenga en cuenta que hacerlo no es muy seguro. El mecanismo de desenrollado de la pila detrás de las excepciones de ObjC (y C ++) es frágil y fundamentalmente incompatible con ARC. Esto es parte de por qué Apple usa excepciones solo para errores de programador: la idea es que usted puede (en teoría, al menos) resolver todos los casos de excepción en su aplicación durante el desarrollo, y que no ocurran excepciones en su código de producción. (Los errores NSError o NSError s, por otro lado, pueden indicar errores situacionales o de usuario recuperables).

La solución más segura es prever las condiciones probables que podrían hacer que una API arroje excepciones y las maneje antes de llamar a la API. Si está indexando en un NSArray , verifique primero su count . Si está configurando launchPath en una NSTask en algo que podría no existir o no ser ejecutable, use NSFileManager para verificar eso antes de iniciar la tarea.


Lo que sugiero es hacer una función en C que detecte la excepción y devuelva un NSError. Y luego, usa esta función.

La función podría verse así:

NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *)) { NSError *error = nil; @try { tryBlock(); } @catch (NSException *exception) { error = convertNSException(exception); } @finally { return error; } }

Y con un poco de ayuda puente, solo tendrá que llamar:

if let error = tryCatch(task.launch, myConvertFunction) { print("An exception happened!", error.localizedDescription) // Do stuff } // Continue task

Nota: Realmente no lo probé, no pude encontrar una manera rápida y fácil de tener Objective-C y Swift en un área de juegos.


TL; DR: Use Carthage para incluir https://github.com/eggheadgames/SwiftTryCatch CalculateCatch o CocoaPods para incluir https://github.com/ravero/SwiftTryCatch CalculateCatch .

Luego puede usar un código como este sin temor a que bloquee su aplicación:

import Foundation import SwiftTryCatch class SafeArchiver { class func unarchiveObjectWithFile(filename: String) -> AnyObject? { var data : AnyObject? = nil if NSFileManager.defaultManager().fileExistsAtPath(filename) { SwiftTryCatch.tryBlock({ data = NSKeyedUnarchiver.unarchiveObjectWithFile(filename) }, catchBlock: { (error) in Logger.logException("SafeArchiver.unarchiveObjectWithFile") }, finallyBlock: { }) } return data } class func archiveRootObject(data: AnyObject, toFile : String) -> Bool { var result: Bool = false SwiftTryCatch.tryBlock({ result = NSKeyedArchiver.archiveRootObject(data, toFile: toFile) }, catchBlock: { (error) in Logger.logException("SafeArchiver.archiveRootObject") }, finallyBlock: { }) return result } }

La respuesta aceptada por @BPCorp funciona según lo previsto, pero como descubrimos, las cosas se vuelven un poco interesantes si intenta incorporar este código de Objective C en un marco Swift mayoritario y luego ejecuta pruebas. Tuvimos problemas con la función de clase que no se encuentra ( Error: uso de identificador no resuelto ). Entonces, por esa razón, y solo por la facilidad general de uso, lo empaquetamos como una biblioteca de Cartago para uso general.

Curiosamente, podríamos usar el marco Swift + ObjC en otro lugar sin problemas, solo las pruebas unitarias para el marco tenían problemas.

PR solicitados! (Sería bueno tener una combinación de CocoaPod y Carthage, así como tener algunas pruebas).