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

50
demandé sur silyevsk 2015-09-24 13:16:32

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"
96
répondu freytag 2017-04-28 21:20:52

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.

12
répondu BPCorp 2016-07-07 23:28:19

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

8
répondu mm2001 2015-12-28 22:10:29

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.

2
répondu rickster 2015-12-28 23:18:08