Le code suivant dans Swift déclenche une exception NSInvalidArgumentException:
task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()
Comment puis-je attraper l'exception? Si j'ai bien compris, essayer/attraper dans Swift concerne les erreurs renvoyées dans Swift, pas les exceptions NSException générées à partir d'objets tels que NSTask (qui, je suppose, est écrit en ObjC). Je suis nouveau sur Swift, alors il se peut qu'il me manque quelque chose d'évident ...
Edit : voici un radar pour le bogue (spécialement pour NSTask): openradar.appspot.com/22837476
Voici un 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(^)(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
N'oubliez pas d'ajouter ceci à votre "* -Bridging-Header.h":
#import "ObjC.h"
Ce que je suggère est de créer une fonction C qui capturera l'exception et retournera un 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, vous devrez simplement appeler:
if let error = tryCatch(task.launch, myConvertFunction) {
print("An exception happened!", error.localizedDescription)
// Do stuff
}
// Continue task
Remarque: 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: utilisez Carthage pour inclure https://github.com/eggheadgames/SwiftTryCatch ou CocoaPods à inclure https://github.com/ravero/SwiftTryCatch .
Vous pouvez ensuite utiliser un code comme celui-ci sans crainte de bloquer 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 cadre majoritaire Swift, puis des tests. problèmes avec la fonction de classe non trouvée ( Erreur: Utilisation de l'identificateur non résolu). Donc, pour cette raison, et pour la facilité d'utilisation générale, nous l'avons présentée sous forme de bibliothèque Carthage à usage général.
Bizarrement, nous pourrions utiliser le framework Swift + ObjC ailleurs, sans aucun problème, c’est uniquement les tests unitaires du framework qui rencontraient des difficultés.
PR demandés! (Ce serait bien de pouvoir combiner CocoaPod et Carthage avec des tests).
Comme indiqué dans les commentaires, le fait que cette API génère des exceptions pour des conditions d'échec récupérables est un bogue. Fichier et demande une alternative basée sur NSError. La plupart du temps, l'état actuel des choses est un anachronisme, car NSTask
date du retour avant Apple normalisé sur avoir des exceptions être pour les erreurs de programmeur seulement.
En attendant, pendant que vous pouvez utiliser l'un des mécanismes d'autres réponses pour intercepter des exceptions dans ObjC et les transmettre à Swift, sachez que cela n'est pas le cas. pas très sûr. Le mécanisme de déroulement de pile derrière les exceptions ObjC (et C++) est fragile et fondamentalement incompatible avec ARC. C'est en partie pourquoi Apple utilise des exceptions uniquement pour les erreurs de programmation. L'idée étant que vous pouvez (théoriquement, au moins) résoudre tous les cas d'exception de votre application pendant le développement et ne pas avoir d'exceptions. se produisant dans votre code de production (les erreurs Swift ou NSError
s, par contre, peuvent indiquer des erreurs récupérables liées à la situation ou à un utilisateur).
La solution la plus sûre consiste à prévoir les conditions probables pouvant amener une API à émettre 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
d'un NSTask
sur un élément inexistant ou non exécutable, utilisez NSFileManager
pour le vérifier avant de lancer la tâche.
Comme indiqué dans la documentation , il s'agit d'un moyen simple et léger de le faire:
do {
try fileManager.moveItem(at: fromURL, to: toURL)
} catch let error as NSError {
print("Error: \(error.domain)")
}