web-dev-qa-db-fra.com

Que signifie "erreur fatale: valeur inopinée trouvée lors du déballage d'une valeur facultative"?

Mon programme Swift se bloque avec EXC_BAD_INSTRUCTION et cette erreur. Qu'est-ce que cela signifie et comment puis-je résoudre ce problème?

erreur fatale: trouvé de manière inattendue nil lors du déroulement d'une valeur facultative


Cet article a pour but de collecter les réponses aux problèmes "de manière inattendue", afin qu'ils ne soient pas dispersés et difficiles à trouver. N'hésitez pas à ajouter votre propre réponse ou éditer la réponse du wiki existant.

375
jtbandes

Réponse TL; DR

Avec très peu d'exceptions , cette règle est d'or:

Évitez d'utiliser !

Déclarer une variable facultative (?), non optionnelle non déballée (IUO) (!)

En d'autres termes, utilisez plutôt:
var nameOfDaughter: String?

Au lieu de:
var nameOfDaughter: String!

Déballer la variable optionnelle en utilisant if let ou guard let

Soit dérouler la variable comme ceci:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

Ou comme ceci:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

Cette réponse se voulait concise, pour une compréhension complète, lisez réponse acceptée

62
Sajjon

Cette question revient TOUT LE TEMPS sur SO. C'est l'un des premiers problèmes avec lesquels les nouveaux développeurs Swift se débattent.

Contexte:

Swift utilise le concept "optionnel" pour traiter les valeurs pouvant contenir une valeur ou non. Dans d'autres langues comme C, vous pouvez stocker une valeur de 0 dans une variable pour indiquer qu'elle ne contient aucune valeur. Cependant, que se passe-t-il si 0 est une valeur valide? Ensuite, vous pourriez utiliser -1. Et si -1 est une valeur valide? Etc.

Les options Swift vous permettent de configurer une variable de tout type pour contenir une valeur valide ou aucune valeur.

Vous mettez un point d'interrogation après le type lorsque vous déclarez une variable comme moyenne (tapez x ou aucune valeur).

Un optionnel est en fait un conteneur qui contient soit une variable d'un type donné, soit rien.

Un optionnel doit être "décompressé" pour récupérer la valeur à l'intérieur.

Le "!" opérateur est un opérateur "déballer". Il dit "faites-moi confiance. Je sais ce que je fais. Je garantis que, lorsque ce code sera exécuté, la variable ne contiendra pas zéro". Si vous vous trompez, vous vous plantez.

À moins que vous ne sachiez vraiment ce que vous faites, évitez le "!" forcer l'opérateur à dérouler. C’est probablement la source la plus importante de crash pour les programmeurs débutants Swift.

Comment traiter les optionnels:

Il y a beaucoup d'autres façons de gérer les options en toute sécurité. En voici quelques unes (liste non exhaustive)

Vous pouvez utiliser "binding optionnel" ou "if let" pour dire "si cet élément facultatif contient une valeur, enregistrez cette valeur dans une nouvelle variable non facultative. Si l'élément facultatif ne contient pas de valeur, ignorez le corps de cette instruction if ".

Voici un exemple de liaison optionnelle avec notre foo optional:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

Notez que la variable que vous définissez lorsque vous utilisez un lien optionnel existe uniquement (est seulement "dans la portée") dans le corps de l'instruction if.

Vous pouvez également utiliser une instruction guard qui vous permet de quitter votre fonction si la variable est nil:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

Des instructions Guard ont été ajoutées dans Swift 2. Guard vous permet de conserver le "chemin d'accès en or" dans votre code et d'éviter les niveaux toujours croissants de if imbriqués résultant parfois de l'utilisation de la liaison facultative "if let".

Il existe également une construction appelée "opérateur de coalescence nul". Il se présente sous la forme "optional_var ?? replacement_val". Il renvoie une variable non facultative du même type que les données contenues dans la variable facultative. Si facultatif contient la valeur nil, il renvoie la valeur de l'expression après le caractère "??" symbole.

Donc, vous pouvez utiliser un code comme ceci:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

Vous pouvez également utiliser try/catch ou guarder la gestion des erreurs, mais l’une des techniques ci-dessus est généralement plus propre.

MODIFIER:

Un autre, légèrement plus subtil avec les options, est "les options implicitement non enveloppées. Lorsque nous déclarons foo, nous pourrions dire:

var foo: String!

Dans ce cas, foo est toujours une option, mais vous n’avez pas besoin de le dérouler pour le référencer. Cela signifie que chaque fois que vous essayez de faire référence à foo, vous plantez s’il est nul.

Donc ce code:

var foo: String!


let upperFoo = foo.capitalizedString

Crashera en référence à la propriété capitalizedString de foo même si nous ne sommes pas en train de dérouler de force foo l'impression est bonne, mais ça ne l'est pas.

Ainsi, vous voulez être très prudent avec les options implicitement non enveloppées. (et peut-être même les éviter complètement jusqu'à ce que vous ayez une solide compréhension des optionnels.)

Conclusion: lorsque vous apprenez Swift pour la première fois, prétendez que le "!" le caractère ne fait pas partie de la langue. Cela risque de vous causer des ennuis.

39
Duncan C

Depuis les réponses ci-dessus explique clairement comment jouer en toute sécurité avec Optionals. Je vais essayer d’expliquer ce que sont les optionnels dans Swift.

Une autre façon de déclarer une variable optionnelle est

var i : Optional<Int>

Et le type facultatif n’est autre chose qu’une énumération à deux cas, c.-à-d.

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Donc, assigner un zéro à notre variable 'i'. Nous pouvons faire var i = Optional<Int>.none ou assigner une valeur, nous allons passer une valeur var i = Optional<Int>.some(28)

Selon Swift, "zéro" est l'absence de valeur. Et pour créer une instance initialisée avec nil, nous devons nous conformer à un protocole appelé ExpressibleByNilLiteral, et super si vous l'avez deviné, seul Optionals est conforme à ExpressibleByNilLiteral et à d'autres types. est découragé.

ExpressibleByNilLiteral a une seule méthode appelée init(nilLiteral:) qui initialise une instance avec nil. Vous n’appellerez généralement pas cette méthode et, conformément à la documentation Swift, il est déconseillé d’appeler directement cet initialiseur, car le compilateur l’appelle chaque fois que vous initialisez un type Optional avec nil literal.

Même moi-même, je dois tourner la tête (sans jeu de mots) Optionals: D Happy Swfting All.

11
Tharzeez

Tout d’abord, vous devez savoir ce qu’est une valeur facultative. Vous pouvez passer à Le Swift Programme de lancement

pour les détails.

Deuxièmement, vous devez savoir que la valeur facultative a deux statuts. L'un est la valeur complète et l'autre est la valeur nulle. Donc, avant d’implémenter une valeur optionnelle, vous devez vérifier l’état correspondant.

Vous pouvez utiliser if let ... ou guard let ... else et ainsi de suite.

Une autre façon, si vous ne voulez pas vérifier son état avant votre implémentation, vous pouvez également utiliser var buildingName = buildingName ?? "buildingName" à la place.

11
QiunCheng

J'ai eu cette erreur une fois lorsque j'essayais de définir les valeurs de Outlets à partir de la méthode prepare for segue comme suit:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // This line pops up the error
            destination.nameLabel.text = item.name
        }
    }
}

Ensuite, j'ai découvert que je ne pouvais pas définir les valeurs des prises du contrôleur de destination car le contrôleur n'avait pas encore été chargé ou initialisé.

Alors je l'ai résolu de cette façon:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // Created this method in the destination Controller to update its outlets after it's being initialized and loaded
            destination.updateView(itemData:  item)
        }
    }
}

Contrôleur de destination:

// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""

// Outlets
@IBOutlet weak var nameLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    nameLabel.text = name
}

func updateView(itemDate: ObjectModel) {
    name = itemDate.name
}

J'espère que cette réponse aide tout le monde avec le même problème car j'ai trouvé que la réponse indiquée est une excellente ressource pour la compréhension des options et de leur fonctionnement, mais ne s'est pas directement attaquée au problème.

6
Wissa

En gros, vous avez essayé d'utiliser une valeur nulle dans les endroits où Swift n'autorise que les valeurs non nuls, en disant au compilateur de vous faire confiance qu'il n'y aura jamais de valeur nulle, permettant ainsi à votre application de se compiler.

Plusieurs scénarios conduisent à ce type d'erreur fatale:

  1. déroulements forcés:

    let user = someVariable!
    

    Si someVariable est nul, vous aurez un crash. En procédant à un déroulage forcé, vous avez transféré la responsabilité de vérification nulle du compilateur, essentiellement en effectuant un déroulage forcé, vous garantissez au compilateur que vous n’y trouverez jamais de valeur nulle. Et devinez ce qui se passe si, d'une manière ou d'une autre, une valeur nulle se termine par someVariable?

    Solution? Utilisez la liaison optionnelle (aka if-let), faites le traitement de la variable ici:

    if user = someVariable {
        // do your stuff
    }
    
  2. moulages forcés (vers le bas):

    let myRectangle = someShape as! Rectangle
    

    Ici, par force, vous dites au compilateur de ne plus s’inquiéter, car vous aurez toujours une instance Rectangle. Et tant que cela dure, vous n'avez pas à vous inquiéter. Les problèmes commencent lorsque vous ou vos collègues du projet commencez à faire circuler des valeurs non rectangulaires.

    Solution? Utilisez la liaison optionnelle (aka if-let), faites le traitement de la variable ici:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
    
  3. Les options implicitement non emballées. Supposons que vous ayez la définition de classe suivante:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }
    

    Désormais, si personne ne gâche la propriété name en le définissant sur nil, il fonctionne comme prévu. Toutefois, si User est initialisé à partir d'un JSON auquel manque le name _, vous obtenez l'erreur fatale lorsque vous essayez d'utiliser la propriété.

    Solution? Ne les utilisez pas :) Sauf si vous êtes sûr à 102% que la propriété aura toujours une valeur non nulle au moment de son utilisation. Dans la plupart des cas, la conversion en option ou non facultative fonctionnera. Le rendre non facultatif aura également pour résultat que le compilateur vous aidera en indiquant les chemins de code que vous avez manqués en donnant une valeur à cette propriété.

  4. Points de vente non connectés ou pas encore connectés. C'est un cas particulier du scénario n ° 3. En gros, vous voulez utiliser une classe chargée par XIB.

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }
    

    Maintenant, si vous avez manqué de connecter la prise à partir de l'éditeur XIB, l'application se bloquera dès que vous souhaiterez utiliser la prise. Solution? Assurez-vous que toutes les prises sont connectées. Ou utilisez l'opérateur ? sur eux: emailTextField?.text = "[email protected]". Ou déclarez la sortie comme étant facultative, bien que dans ce cas le compilateur vous oblige à tout décompresser dans le code.

  5. Les valeurs provenant d'Objective-C, et qui n'ont pas d'annotations de nullabilité. Supposons que nous ayons la classe Objective-C suivante:

    @interface MyUser: NSObject
    @property NSString *name;
    @end
    

    Maintenant, si aucune annotation de nullabilité n'est spécifiée (soit explicitement, soit via NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END), la propriété name sera importée dans Swift comme String! (un IUO - implicitement non emballé facultatif). Dès que du code Swift voudra utiliser la valeur, il plantera si name est nul.

    Solution? Ajoutez des annotations nullables à votre code Objective-C. Attention cependant, le compilateur Objective-C est un peu permissif en ce qui concerne la nullité, vous pouvez vous retrouver avec des valeurs nil, même si vous les avez explicitement marquées comme nonnull.

6
Cristik

Les erreurs EXC_BAD_INSTRUCTION et fatal error: unexpectedly found nil while unwrapping an Optional value apparaissent le plus souvent lorsque vous avez déclaré un @IBOutlet, mais que vous n'êtes pas connecté au storyboard.

Vous devriez également vous renseigner sur le fonctionnement de Optionsals, mentionné dans d’autres réponses, mais c’est le seul moment qui m’apparaît le plus souvent.

0
Ale Mohamad