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).