Swift 2: L'appel peut lancer, mais il n'est pas marqué avec 'try' et l'erreur n'est pas gérée

161

Après avoir installé Xcode 7 beta et converti mon code swift en Swift 2, j'ai eu un problème avec le code que je ne peux pas comprendre. Je sais que Swift 2 est nouveau, donc je cherche et je comprends car il n'y a rien à ce sujet, je devrais écrire une question.

Voici l'erreur:

L'appel peut lancer, mais il n'est pas marqué avec 'try' et l'erreur n'est pas gérée

Code:

func deleteAccountDetail(){
        let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
        let request = NSFetchRequest()
        request.entity = entityDescription

        //The Line Below is where i expect the error
        let fetchedEntities = self.Context!.executeFetchRequest(request) as! [AccountDetail]

        for entity in fetchedEntities {
        self.Context!.deleteObject(entity)
        }

        do {
            try self.Context!.save()
        } catch _ {
        }

    }

Instantané: entrez la description de l'image ici

Farhad
la source

Réponses:

168

Vous devez attraper l'erreur comme vous le faites déjà pour votre save()appel et comme vous gérez plusieurs erreurs ici, vous pouvez tryplusieurs appels séquentiellement dans un seul bloc do-catch, comme ceci:

func deleteAccountDetail() {
    let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
    let request = NSFetchRequest()
    request.entity = entityDescription

    do {
        let fetchedEntities = try self.Context!.executeFetchRequest(request) as! [AccountDetail]

        for entity in fetchedEntities {
            self.Context!.deleteObject(entity)
        }

        try self.Context!.save()
    } catch {
        print(error)
    }
}

Ou comme @ bames53 l'a souligné dans les commentaires ci-dessous, il est souvent préférable de ne pas détecter l'erreur là où elle a été lancée. Vous pouvez marquer la méthode comme throwsalors trypour appeler la méthode. Par exemple:

func deleteAccountDetail() throws {
    let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
    let request = NSFetchRequest()

    request.entity = entityDescription

    let fetchedEntities = try Context.executeFetchRequest(request) as! [AccountDetail]

    for entity in fetchedEntities {
        self.Context!.deleteObject(entity)
    }

    try self.Context!.save()
}
Mick MacCallum
la source
Cela m'aide à comprendre, merci.
Farhad le
5
En fait, il n'est pas nécessaire que l'exception soit interceptée ici. Il est possible d'ajouter simplement le trymot - clé à l'appel de fonction et de déclarer cette fonction comme func deleteAccountDetail() throw. Ou si vous avez garanti que la fonction ne lancera pas pour l'entrée donnée, vous pouvez utiliser try!.
bames53
4
J'évoque cela non pas pour pinailler, mais parce qu'il est en fait assez important pour une gestion correcte des erreurs basée sur les exceptions que la plupart des endroits où des exceptions se produisent ne les détectent pas. Il existe trois types d'endroits où il convient de détecter les exceptions. Dans tous les autres endroits, le code ne doit pas gérer les exceptions de manière explicite, et doit s'appuyer sur des deinit()appels implicites pour effectuer le nettoyage (c'est-à-dire RAII), ou parfois utiliser deferpour effectuer un nettoyage ad hoc. Voir exceptionsafecode.com pour plus d'informations (il parle de C ++, mais les principes de base s'appliquent également aux exceptions Swift.)
bames53
Mais comment exécuteriez-vous la fonction? Si je vais avec @ bames53 way?
Farhad le
1
@NickMoore Ce que les développeurs Swift choisissent de les appeler ne fait aucune différence dans ce qu'ils sont réellement. Le nouveau système de gestion des erreurs de Swift est une implémentation d'exceptions car ce terme est couramment utilisé dans le reste de l'industrie.
bames53
41

Lorsque vous appelez une fonction déclarée avec throwsdans Swift, vous devez annoter le site d'appel de fonction avec tryou try!. Par exemple, étant donné une fonction de lancement:

func willOnlyThrowIfTrue(value: Bool) throws {
  if value { throw someError }
}

cette fonction peut être appelée comme:

func foo(value: Bool) throws {
  try willOnlyThrowIfTrue(value)
}

Ici, nous annotons l'appel avec try, qui appelle le lecteur que cette fonction peut lever une exception et que les lignes de code suivantes peuvent ne pas être exécutées. Nous avons aussi annoter cette fonction avec throws, parce que cette fonction pourrait lancer une exception (quand willOnlyThrowIfTrue()lancers francs, puis foosera automatiquement réémettre l'exception vers le haut.

Si vous voulez appeler une fonction qui est déclarée comme pouvant être lancée, mais dont vous savez qu'elle ne la lancera pas dans votre cas parce que vous lui donnez une entrée correcte, vous pouvez utiliser try!.

func bar() {
  try! willOnlyThrowIfTrue(false)
}

De cette façon, lorsque vous garantissez que le code ne sera pas lancé, vous n'avez pas à insérer de code standard supplémentaire pour désactiver la propagation des exceptions.

try!est appliqué à l'exécution: si vous utilisez try!et que la fonction finit par lancer, l'exécution de votre programme se terminera par une erreur d'exécution.

La plupart des codes de gestion d'exceptions devraient ressembler à ce qui précède: soit vous propagez simplement les exceptions vers le haut lorsqu'elles se produisent, soit vous définissez des conditions telles que les exceptions éventuelles sont exclues. Tout nettoyage des autres ressources de votre code doit se faire via la destruction d'objets (ie deinit()), ou parfois via le defercode ed.

func baz(value: Bool) throws {

  var filePath = NSBundle.mainBundle().pathForResource("theFile", ofType:"txt")
  var data = NSData(contentsOfFile:filePath)

  try willOnlyThrowIfTrue(value)

  // data and filePath automatically cleaned up, even when an exception occurs.
}

Si, pour une raison quelconque, vous avez du code de nettoyage qui doit s'exécuter mais qui n'est pas deinit() fonction, vous pouvez utiliser defer.

func qux(value: Bool) throws {
  defer {
    print("this code runs when the function exits, even when it exits by an exception")
  }

  try willOnlyThrowIfTrue(value)
}

La plupart du code qui traite les exceptions les fait simplement se propager vers le haut aux appelants, effectuant le nettoyage en cours de route via deinit()ou defer. C'est parce que la plupart du code ne sait pas quoi faire avec les erreurs; il sait ce qui s'est mal passé, mais il n'a pas suffisamment d'informations sur ce qu'un code de niveau supérieur essaie de faire pour savoir quoi faire à propos de l'erreur. Il ne sait pas si la présentation d'une boîte de dialogue à l'utilisateur est appropriée, ou s'il doit réessayer, ou si quelque chose d'autre est approprié.

Cependant, le code de niveau supérieur doit savoir exactement quoi faire en cas d'erreur. Ainsi, les exceptions permettent à des erreurs spécifiques de remonter de l'endroit où elles se produisent initialement à l'endroit où elles peuvent être traitées.

La gestion des exceptions se fait via catch instructions.

func quux(value: Bool) {
  do {
    try willOnlyThrowIfTrue(value)
  } catch {
    // handle error
  }
}

Vous pouvez avoir plusieurs instructions catch, chacune interceptant un type d'exception différent.

  do {
    try someFunctionThatThowsDifferentExceptions()
  } catch MyErrorType.errorA {
    // handle errorA
  } catch MyErrorType.errorB {
    // handle errorB
  } catch {
    // handle other errors
  }

Pour plus d'informations sur les bonnes pratiques avec exceptions, consultez http://exceptionsafecode.com/ . Il est spécifiquement destiné au C ++, mais après avoir examiné le modèle d'exception Swift, je pense que les bases s'appliquent également à Swift.

Pour plus de détails sur la syntaxe Swift et le modèle de gestion des erreurs, consultez le livre The Swift Programming Language (Swift 2 Prerelease) .

bames53
la source
Fondamentalement, se rattraper peut gérer l'erreur? ou fonction d'entrée
Farhad
1
@BrianS Je ne sais pas exactement ce que vous demandez, en particulier en ce qui concerne une «fonction d'entrée», mais «catch» est essentiellement un synonyme de «handle» dans le contexte des exceptions. C'est-à-dire qu'attraper une exception et gérer une exception sont la même chose, en ce qui concerne les langages de programmation.
bames53
J'ai une erreur que je ne comprends pas, pouvez-vous m'aider s'il vous plaît? Invalid conversion from throwing function of type '() throws -> _' to non-throwing function type '(NSData?, NSURLResponse?, NSError?) -> Void'
Farhad le
@BrianS On dirait que vous utilisez une fonction avec une signature incorrecte quelque part. Quelque chose s'attend à recevoir une fonction qui prend NSData?, NSURLResponse?, NSError?comme arguments, mais vous lui donnez une fonction qui ne prend aucun argument.
bames53
Ou quelque chose s'attend à ce qu'une fonction non déclarée lève des exceptions et vous lui donnez une fonction qui lève des exceptions.
bames53