Capture NSException dans Swift
Le code suivant dans Swift déclenche l'exception nsinvalidargumentexception:
task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()
Comment puis-je attraper l'exception? Si je comprends bien, try / catch dans Swift est pour les erreurs lancées dans Swift, pas pour NSExceptions soulevées à partir d'objets comme NSTask (que je suppose est écrit dans ObjC). Je suis nouveau à Swift donc peut-être que je manque quelque chose d'évident...
Edit: voici un radar pour le bug (en particulier pour NSTask): openradar.appspot.com/22837476
4 réponses
Voici du code, qui convertit NSExceptions En erreurs Swift 2.
Maintenant, vous pouvez utiliser
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(^)())tryBlock error:(__autoreleasing NSError **)error;
@end
ObjC.m
#import "ObjC.h"
@implementation ObjC
+ (BOOL)catchException:(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
N'oubliez pas d'ajouter ceci à votre " * -Bridging-Header.h":
#import "ObjC.h"
Ce que je suggère est de faire une fonction C qui va attraper l'exception et retourner une NSError à la place. Et puis, utilisez cette fonction.
La fonction pourrait ressembler à ceci:
NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *))
{
NSError *error = nil;
@try {
tryBlock();
}
@catch (NSException *exception) {
error = convertNSException(exception);
}
@finally {
return error;
}
}
Et avec un peu d'aide de transition, vous aurez juste à appeler:
if let error = tryCatch(task.launch, myConvertFunction) {
print("An exception happened!", error.localizedDescription)
// Do stuff
}
// Continue task
Note: Je ne l'ai pas vraiment testé, Je ne pouvais pas trouver un moyen rapide et facile D'avoir Objective-C et Swift dans un terrain de jeu.
TL;DR: l'Utilisation de Carthage à inclure https://github.com/eggheadgames/SwiftTryCatch ou CocoaPods à inclure https://github.com/ravero/SwiftTryCatch.
Ensuite, vous pouvez utiliser du code comme celui-ci sans crainte qu'il ne Plante votre application:
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 réponse acceptée par @bpcorp fonctionne comme prévu, mais comme nous l'avons découvert, les choses deviennent un peu intéressantes si vous essayez d'incorporer ce code Objective C dans un framework Swift majoritaire, puis exécutez des tests. Nous a eu des problèmes avec la fonction de classe introuvable (erreur: utilisation de l'identifiant non résolu ). Donc, pour cette raison, et juste la facilité d'utilisation générale, nous l'avons emballé comme une bibliothèque de Carthage pour un usage général.
étrangement, nous pourrions utiliser le framework Swift + ObjC ailleurs sans aucun problème, ce ne sont que les tests unitaires du framework qui étaient en difficulté.
PRs demandé! (Ce serait bien de l'avoir un combo CocoaPod & Carthage build, ainsi que d'en avoir test).
Comme indiqué dans les commentaires, que cette API lève des exceptions pour des conditions d'échec récupérables par ailleurs est un bug. déposez-le et demandez une alternative basée sur NSError. la plupart du temps, l'état actuel des choses est un anachronisme, car NSTask
Date de retour avant que Apple ne standardise les exceptions pour les erreurs de programmeur uniquement.
En attendant, alors que vous pourriez utiliser l'un des mécanismes d'autres réponses pour attraper des exceptions dans ObjC et les transmettre à Swift, soyez conscient qui n'est pas très sûr. Le mécanisme de déroulement de la pile derrière les exceptions ObjC (et c++) est fragile et fondamentalement incompatible avec ARC. Cela explique en partie pourquoi Apple utilise des exceptions uniquement pour les erreurs de programmeur - l'idée étant que vous pouvez (théoriquement, au moins) trier tous les cas d'exception dans votre application pendant le développement, et qu'aucune exception ne se produit dans votre code de production. (Les erreurs Swift ou NSError
s, d'autre part, peuvent indiquer une situation ou un utilisateur récupérable erreur.)
La solution la plus sûre consiste à prévoir les conditions probables qui pourraient amener une API à lancer des exceptions et à les gérer avant d'appeler L'API. Si vous indexez dans un NSArray
, Vérifiez d'abord son count
. Si vous définissez le launchPath
sur un NSTask
sur quelque chose qui pourrait ne pas exister ou ne pas être exécutable, utilisez NSFileManager
pour vérifier cela avant de lancer la tâche.