web-dev-qa-db-fra.com

Traitement des erreurs en langage rapide

Je n'ai pas beaucoup lu dans Swift mais une chose que j'ai remarquée, c'est qu'il n'y a pas d'exception ... Comment gère-t-on les erreurs dans Swift? Quelqu'un at-il trouvé quelque chose à propos de la gestion des erreurs?

181
peko

Swift 2 & 3

Les choses ont un peu changé dans Swift 2, car il existe un nouveau mécanisme de gestion des erreurs, un peu plus similaire aux exceptions mais avec des détails différents.

1. Indiquant une possibilité d'erreur

Si fonction/méthode veut indiquer qu’elle peut générer une erreur, elle devrait contenir le mot clé throws comme celui-ci 

func summonDefaultDragon() throws -> Dragon

Remarque: il n'y a aucune spécification pour le type d'erreur que la fonction peut réellement générer. Cette déclaration indique simplement que la fonction peut générer une instance de tout type implémentant ErrorType ou qu'elle ne lève pas du tout.

2. Invoquer une fonction qui peut générer des erreurs

Pour appeler une fonction, vous devez utiliser le mot-clé try, comme ceci

try summonDefaultDragon()

cette ligne devrait normalement être présente comme bloc do-catch

do {
    let dragon = try summonDefaultDragon() 
} catch DragonError.dragonIsMissing {
    // Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
    // Other specific-case error-handlng
} catch {
    // Catch all error-handling
}

Remarque: la clause catch utilise toutes les fonctionnalités puissantes de la correspondance de motifs Swift, vous êtes donc très flexible ici.

Vous pouvez décider de propager l'erreur si vous appelez une fonction de projection depuis une fonction elle-même marquée avec le mot clé throws:

func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

Vous pouvez également appeler la fonction de projection à l'aide de try?:

let dragonOrNil = try? summonDefaultDragon()

De cette façon, vous obtenez la valeur de retour ou nil, si une erreur survient. De cette façon, vous n'obtenez pas l'objet d'erreur.

Cela signifie que vous pouvez également combiner try? avec des instructions utiles telles que:

if let dragon = try? summonDefaultDragon()

ou

guard let dragon = try? summonDefaultDragon() else { ... }

Enfin, vous pouvez décider que vous savez que l'erreur ne se produira pas réellement (par exemple, car vous avez déjà vérifié les conditions préalables) et utiliser le mot clé try!:

let dragon = try! summonDefaultDragon()

Si la fonction génère une erreur, vous obtiendrez une erreur d'exécution dans votre application et celle-ci se terminera.

3. Lancer une erreur

Pour lancer une erreur, vous utilisez le mot-clé throw tel que celui-ci

throw DragonError.dragonIsMissing

Vous pouvez lancer tout ce qui est conforme au protocole ErrorType. Pour les débutants, NSError est conforme à ce protocole, mais vous voudrez probablement utiliser ErrorType basé sur une énumération, ce qui vous permet de regrouper plusieurs erreurs connexes, éventuellement avec des données supplémentaires, comme celle-ci.

enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

Les principales différences entre le nouveau mécanisme d'erreur Swift 2 et 3 et les exceptions de style Java/C #/C++ sont les suivantes:

  • La syntaxe est un peu différente: do-catch + try + defer par rapport à la syntaxe traditionnelle try-catch-finally.
  • La gestion des exceptions entraîne généralement un temps d'exécution beaucoup plus long dans le chemin des exceptions que dans le chemin du succès. Ce n'est pas le cas des erreurs Swift 2.0, où chemin de réussite et chemin d'erreur coûtent à peu près le même prix.
  • Tout code de rejet d'erreur doit être déclaré, tandis que des exceptions peuvent avoir été levées de n'importe où. Toutes les erreurs sont des "exceptions vérifiées" dans la nomenclature Java. Cependant, contrairement à Java, vous ne spécifiez pas d'erreurs potentiellement générées.
  • Les exceptions rapides ne sont pas compatibles avec les exceptions ObjC. Votre bloc do-catch n'acceptera aucune exception NSException, et inversement, car vous devez utiliser ObjC.
  • Les exceptions Swift sont compatibles avec les conventions de méthode Cocoa NSError qui renvoient soit false (pour les fonctions retournant Bool), soit nil (pour les fonctions retournant AnyObject) et en transmettant NSErrorPointer avec des détails d'erreur.

En tant que sucre syntatique supplémentaire pour faciliter la gestion des erreurs, il existe deux autres concepts

  • actions différées (en utilisant le mot clé defer) qui vous permettent d'obtenir le même effet que les blocs finaux en Java/C #/etc
  • instruction Guard (utilisant le mot clé guard) qui vous permet d'écrire un peu moins de code if/else qu'en code d'erreur normal de vérification/signalisation.

Swift 1

Erreurs d'exécution:

Comme Leandros le suggère pour la gestion des erreurs d’exécution (problèmes de connectivité réseau, analyse de données, ouverture de fichier, etc.), vous devez utiliser NSError comme vous le faisiez dans ObjC, car Foundation, AppKit, UIKit, etc. signalent leurs erreurs de cette façon. Donc, c'est plus une chose de cadre qu'une chose de langage.

Les blocs de succès/échec de séparateur, comme dans AFNetworking, sont un autre motif fréquemment utilisé:

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
    success: { (NSURLSessionDataTask) -> Void in
        println("Success")
    },
    failure:{ (NSURLSessionDataTask, NSError) -> Void in
        println("Failure")
    })

Néanmoins, le bloc d’échec reçoit fréquemment une instance NSError décrivant l’erreur.

Erreurs de programmeur:

Pour les erreurs de programmeur (comme l'accès hors limites de l'élément de tableau, les arguments non valides passés à un appel de fonction, etc.), vous avez utilisé des exceptions dans ObjC. La langue rapide ne semble pas avoir de prise en charge linguistique pour les exceptions (comme le mot clé throw, catch, etc.). Cependant, comme la documentation le suggère, il est exécuté sur le même runtime qu'ObjC et vous pouvez donc toujours lancer NSExceptions comme ceci:

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

Vous ne pouvez simplement pas les attraper dans Swift pur, bien que vous puissiez opter pour des exceptions dans le code ObjC.

La question est de savoir si vous devriez lever des exceptions pour les erreurs de programmation, ou plutôt utiliser des assertions, comme le suggère Apple dans le guide des langues.

142
MDJ

Mise à jour du 9 juin 2015 - Très important

Swift 2.0 est livré avec les mots clés try, throw et catch et le plus intéressant est: 

Swift convertit automatiquement les méthodes Objective-C qui produisent des erreurs en méthodes émettant une erreur conformément à la fonctionnalité de gestion des erreurs native de Swift.

Remarque: les méthodes qui consomment des erreurs, telles que les méthodes de délégation ou les méthodes qui prennent un gestionnaire d'achèvement avec un argument d'objet NSError, ne pas deviennent des méthodes qui jettent lorsqu’elles sont importées par Swift.

Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C (Swift 2 Prerelease).» IBooks.

Exemple: (du livre)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

L'équivalent dans Swift sera:

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

Lancer une erreur:

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

Sera automatiquement propagé à l'appelant:

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

D'après les livres Apple, le langage de programmation Swift, il semble que les erreurs doivent être traitées avec enum.

Voici un exemple tiré du livre.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

De: Apple Inc. «Le langage de programmation Swift». IBooks. https://itun.es/br/jEUH0.l

Mettre à jour

Dans les livres d'actualités Apple, "Utilisation de Swift avec Cocoa et Objective-C". Les exceptions d'exécution ne se produisent pas avec les langues Swift, c'est pourquoi vous n'avez pas try-catch. Au lieu de cela, vous utilisez Chaînage optionnel .

Voici un extrait du livre:

Par exemple, dans la liste de codes ci-dessous, les première et deuxième lignes sont non exécuté car la propriété length et le characterAtIndex: méthode n'existent pas sur un objet NSDate. La constante myLength est inféré comme étant un Int facultatif, défini sur nil. Vous pouvez également utiliser un instruction if – let pour décompresser conditionnellement le résultat d'une méthode qui l'objet peut ne pas répondre, comme indiqué à la ligne trois

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C.» IBooks. https://itun.es/br/1u3-0.l


Et les livres vous encouragent également à utiliser le motif d'erreur de cacao d'Objective-C (NSError Object) 

Les erreurs signalées dans Swift suivent le même schéma que dans Objective-C, avec l'avantage supplémentaire d'offrir un retour facultatif valeurs. Dans le cas le plus simple, vous renvoyez une valeur Bool à partir de fonction pour indiquer si elle a réussi ou non. Quand vous en avez besoin signaler la raison de l'erreur, vous pouvez ajouter à la fonction un Paramètre NSError out de type NSErrorPointer. Ce type est à peu près équivalent à NSError ** d’Objective-C, avec une sécurité mémoire supplémentaire et saisie facultative. Vous pouvez utiliser le préfixe & l'opérateur pour transmettre un référence à un type NSError facultatif en tant qu'objet NSErrorPointer, en tant que indiqué dans la liste de codes ci-dessous.

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

Extrait de: Apple Inc. «Utilisation de Swift avec Cocoa et Objective-C.» IBooks. https://itun.es/br/1u3-0.l

69

Il n'y a pas d'exception dans Swift, semblable à l'approche d'Objective-C.

En développement, vous pouvez utiliser assert pour détecter les erreurs qui pourraient apparaître et qui doivent être corrigées avant de passer en production.

L’approche NSError classique n’est pas modifiée, vous envoyez une NSErrorPointer qui est remplie.

Bref exemple: 

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}
13
Leandros

La 'Voie Rapide' recommandée est:

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

Cependant, je préfère essayer/attraper car je trouve cela plus facile à suivre car cela déplace la gestion des erreurs vers un bloc séparé à la fin. Cet arrangement est parfois appelé "Chemin d’or". Heureusement, vous pouvez le faire avec des fermetures:

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

De plus, il est facile d’ajouter une nouvelle tentative:

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

La liste de TryBool est:

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

Vous pouvez écrire une classe similaire pour tester une valeur renvoyée Optional au lieu d'une valeur Bool:

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

La version TryOptional applique un type de retour non facultatif qui facilite la programmation ultérieure, par ex. 'Manière rapide:

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

Utilisation de TryOptional:

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

Notez le déroulement automatique.

11
Howard Lovatt

Edit: Bien que cette réponse fonctionne, elle n’est guère plus qu’Objective-C translittérée en Swift. Il a été rendu obsolète par les modifications apportées à Swift 2.0. La réponse de Guilherme Torres Castro ci-dessus est une très bonne introduction à la méthode privilégiée de traitement des erreurs dans Swift. VOS

Il a fallu un peu de temps pour le comprendre, mais je pense l’avoir abordé. Cela semble moche cependant. Rien de plus qu'une peau fine sur la version Objective-C.

Appeler une fonction avec un paramètre NSError ...

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

Ecrire la fonction qui prend un paramètre d'erreur ...

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}
7
Vince O'Sullivan

Emballage de base autour de l’objectif C qui vous donne la fonctionnalité try catch . https://github.com/williamFalcon/SwiftTryCatch

Utilisez comme:

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})
4
William Falcon

Ceci est une réponse mise à jour pour Swift 2.0. Je suis impatient de disposer d'un modèle de traitement des erreurs riche en fonctionnalités, comme en Java. Enfin, ils ont annoncé la bonne nouvelle. ici

Modèle de traitement des erreurs: le nouveau modèle de traitement des erreurs dans Swift 2.0 sera se sentir instantanément naturel, avec essayer, lancer et attraper les mots-clés . Mieux encore, il a été conçu pour fonctionner parfaitement avec les SDK d’Apple et NSError. En fait, NSError est conforme à un type d'erreur Swift. Vous allez vraiment envie de regarder la session WWDC sur les nouveautés de Swift to entendre plus à ce sujet.

par exemple :

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}

Comme le disait Guilherme Torres Castro, dans Swift 2.0, try, catch, do peut être utilisé dans la programmation.

Par exemple, dans la méthode de récupération de données CoreData, au lieu de mettre &error en tant que paramètre dans la fonction managedContext.executeFetchRequest(fetchRequest, error: &error), il suffit maintenant d'utiliser managedContext.executeFetchRequest(fetchRequest), puis de gérer l'erreur avec try, catch ( Lien Document Apple

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

Si vous avez déjà téléchargé la version bêta de xcode7. Essayez de rechercher en jetant des erreurs dans Documentations and API Reference et choisissez le premier résultat affiché. Cela donne une idée de base de ce qui peut être fait pour cette nouvelle syntaxe. Cependant, la documentation complète n'est pas encore publiée pour de nombreuses API. 

Des techniques plus sophistiquées de gestion des erreurs peuvent être trouvées dans 

Quoi de neuf dans Swift (Session 2015 106 28m30s)

3
Zingoer

La gestion des erreurs est une nouvelle fonctionnalité de Swift 2.0. Il utilise les mots clés try, throw et catch.

Voir le Annonce concernant Apple Swift 2.0 sur le blog officiel Apple Swift

2
babelchips

Comme d'autres l'ont déjà mentionné, à partir de Swift 2, la gestion des erreurs est mieux réalisée grâce à l'utilisation d'énumérations do/try/catch et ErrorType. Cela fonctionne assez bien pour les méthodes synchrones, mais un peu d'intelligence est nécessaire pour la gestion des erreurs asynchrones.

Cet article a une excellente approche de ce problème:

https://jeremywsherman.com/blog/2015/06/17/using-Swift-throws-with-completion-callbacks/

Résumer:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

alors, l'appel à la méthode ci-dessus serait comme suit:

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

Cela semble un peu plus simple qu’un rappel séparé par errorHandler transmis à la fonction asynchrone, ce qui était le cas de la manière dont cela serait géré avant Swift 2.

1
Gene Loparco

Ce que j'ai vu, c'est qu'en raison de la nature de l'appareil, vous ne voulez pas envoyer un tas de messages cryptés de gestion des erreurs à l'utilisateur. C’est pourquoi la plupart des fonctions renvoient des valeurs facultatives, il vous suffit donc de coder pour ignorer les options. Si une fonction revient à nil, ce qui signifie qu'elle a échoué, vous pouvez afficher un message ou autre.

0
cheborneck

Bibliothèque simple et agréable pour gérer les exceptions: TryCatchFinally-Swift

Comme quelques autres, il englobe les fonctionnalités d'exception d'Objective C.

Utilisez-le comme ceci:

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}
0
Morten Holmgaard