Que signifie «Erreur fatale: trouvé de façon inattendue nul en déballant une valeur facultative»?

415

Mon programme Swift plante EXC_BAD_INSTRUCTIONet l'une des erreurs similaires suivantes. Que signifie cette erreur et comment la corriger?

Erreur fatale: trouvé de façon inattendue nul lors du déballage d'une valeur facultative

ou

Erreur fatale: trouvé de façon inattendue nil lors du déballage implicite d'une valeur facultative


Ce message est destiné à collecter des réponses aux problèmes "inattendus trouvés nuls", afin qu'ils ne soient pas dispersés et difficiles à trouver. N'hésitez pas à ajouter votre propre réponse ou à modifier la réponse wiki existante.

pkamb
la source
8
@RobertColumbia, il s'agit d'une paire de questions / réponses de Wiki communautaire avec une documentation complète du problème, je ne pense pas que cette question devrait être fermée faute d'un MCVE.
JAL
Créez une variable comme celle-ci [var nameOfDaughter: String? ] et utilisez cette variable comme celle-ci [si let _ = nameOfDaughter {}] est la meilleure approche.
iOS

Réponses:

674

Cette réponse est un wiki communautaire . Si vous pensez qu'il pourrait être amélioré, n'hésitez pas à le modifier !

Contexte: qu'est-ce qu'une option?

Dans Swift, Optionalest un type générique qui peut contenir une valeur (de tout type), ou aucune valeur du tout.

Dans de nombreux autres langages de programmation, une valeur "sentinelle" particulière est souvent utilisée pour manque de valeur . Dans Objective-C, par exemple, nil(le pointeur nul ) indique l'absence d'un objet. Mais cela devient plus délicat lorsque vous travaillez avec des types primitifs - devrait -1être utilisé pour indiquer l'absence d'un entier, ou peut INT_MIN- être , ou d'un autre entier? Si une valeur particulière est choisie pour signifier «aucun entier», cela signifie qu'elle ne peut plus être traitée comme une valeur valide .

Swift est un langage de type sécurisé, ce qui signifie que le langage vous aide à être clair sur les types de valeurs avec lesquelles votre code peut travailler. Si une partie de votre code attend une chaîne, la sécurité de type vous empêche de lui transmettre un Int par erreur.

Dans Swift, tout type peut être rendu facultatif . Une valeur facultative peut prendre n'importe quelle valeur du type d'origine ou de la valeur spéciale nil.

Les options sont définies avec un ?suffixe sur le type:

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

L'absence de valeur dans une option est indiquée par nil:

anOptionalInt = nil

(Notez que ce niln'est pas la même chose que nildans Objective-C. Dans Objective-C, nilest l'absence d'un valide pointeur d'objet , à Swift, Optional ne sont pas limités à des objets / types de référence se comporte en option de façon similaire à Haskell de. Peut - être .)


Pourquoi ai-je obtenu une « erreur fatale: trouvé inopinément nul lors du déballage d'une valeur facultative »?

Pour accéder à la valeur d'une option (si elle en a une), vous devez déballer . Une valeur facultative peut être dépliée en toute sécurité ou de force. Si vous décompressez une option et qu'elle n'a pas de valeur, votre programme se bloquera avec le message ci-dessus.

Xcode vous montrera le crash en mettant en évidence une ligne de code. Le problème se produit sur cette ligne.

ligne écrasée

Ce plantage peut se produire avec deux types différents de déballage forcé:

1. Déballage de force explicite

Cela se fait avec l' !opérateur sur une option. Par exemple:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Erreur fatale: trouvé de façon inattendue nul lors du déballage d'une valeur facultative

Tel anOptionalStringquelnil ici, vous obtiendrez un crash sur la ligne où vous forcez le déballer.

2. Options optionnelles implicitement déballées

Ceux-ci sont définis avec un !, plutôt qu'avec un ?après le type.

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

Ces options sont supposées contenir une valeur. Par conséquent, chaque fois que vous accédez à une option implicitement déballée, celle-ci sera automatiquement forcée pour vous. S'il ne contient pas de valeur, il se bloque.

print(optionalDouble) // <- CRASH

Erreur fatale: trouvé de façon inattendue nil lors du déballage implicite d' une valeur facultative

Afin de déterminer quelle variable a provoqué le crash, vous pouvez maintenir la touche enfoncée tout en cliquant pour afficher la définition, où vous pourriez trouver le type facultatif.

Les IBOutlets, en particulier, sont généralement des options implicitement déballées. En effet, votre xib ou votre storyboard reliera les prises au moment de l'exécution, après l' initialisation. Vous devez donc vous assurer que vous n'accédez pas aux prises avant qu'elles ne soient chargées. Vous devez également vérifier que les connexions sont correctes dans votre fichier storyboard / xib, sinon les valeurs seront nilau moment de l'exécution, et donc se bloqueront lorsqu'elles seront implicitement déballées . Lors de la réparation des connexions, essayez de supprimer les lignes de code qui définissent vos prises, puis reconnectez-les.


Quand devrais-je forcer le déballage d'une option?

Déballage forcé explicite

En règle générale, vous ne devez jamais forcer explicitement le déballage d'une option avec l' !opérateur. Il peut y avoir des cas où l'utilisation !est acceptable - mais vous ne devriez jamais l'utiliser que si vous êtes sûr à 100% que l'option contient une valeur.

Bien qu'il puisse y avoir une occasion où vous pouvez utiliser le dépliage forcé, comme vous le savez pour un fait qu'une option contient une valeur - il n'y a pas un seul endroit où vous ne pouvez pas déballer en toute sécurité cette option à la place.

Options optionnelles implicitement déballées

Ces variables sont conçues pour que vous puissiez reporter leur affectation à plus tard dans votre code. Il est de votre responsabilité de vous assurer qu'ils ont une valeur avant d'y accéder. Cependant, parce qu'ils impliquent un déballage forcé, ils sont toujours intrinsèquement dangereux - car ils supposent que votre valeur n'est pas nulle, même si l'attribution de zéro est valide.

Vous ne devez utiliser les options implicitement déballées qu'en dernier recours . Si vous pouvez utiliser une variable différée ou fournir une valeur par défaut pour une variable - vous devez le faire au lieu d'utiliser une option implicitement non enveloppée.

Cependant, il existe quelques scénarios où les options implicitement déballées sont bénéfiques , et vous pouvez toujours utiliser différentes façons de les déballer en toute sécurité comme indiqué ci-dessous - mais vous devez toujours les utiliser avec la prudence requise.


Comment puis-je traiter en toute sécurité avec les options?

La manière la plus simple de vérifier si une option contient une valeur est de la comparer à nil.

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

Cependant, 99,9% du temps lorsque vous travaillez avec des options, vous souhaiterez réellement accéder à la valeur qu'il contient, si elle en contient une. Pour ce faire, vous pouvez utiliser la liaison facultative .

Reliure optionnelle

La liaison facultative vous permet de vérifier si une option contient une valeur - et vous permet d'affecter la valeur non encapsulée à une nouvelle variable ou constante. Il utilise la syntaxe if let x = anOptional {...}ou if var x = anOptional {...}, selon que vous devez modifier la valeur de la nouvelle variable après l'avoir liée.

Par exemple:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

Ce que cela signifie, c'est d'abord de vérifier que l'option contient une valeur. Si tel est le cas , la valeur 'unfrapped' est affectée à une nouvelle variable ( number) - que vous pouvez ensuite utiliser librement comme si elle n'était pas facultative. Si l'option facultative ne contient pas de valeur, la clause else sera invoquée, comme vous vous en doutez.

Ce qui est bien avec la liaison facultative, c'est que vous pouvez déballer plusieurs options en même temps. Vous pouvez simplement séparer les instructions par une virgule. L'instruction réussira si toutes les options ont été déballées.

var anOptionalInt : Int?
var anOptionalString : String?

if let number = anOptionalInt, let text = anOptionalString {
    print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
    print("One or more of the optionals don’t contain a value")
}

Une autre astuce intéressante est que vous pouvez également utiliser des virgules pour vérifier une certaine condition sur la valeur, après l'avoir déballée.

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

Le seul inconvénient de l'utilisation de la liaison facultative dans une instruction if est que vous ne pouvez accéder à la valeur non encapsulée que dans la portée de l'instruction. Si vous avez besoin d'accéder à la valeur en dehors de la portée de l'instruction, vous pouvez utiliser une instruction guard .

Une instruction de garde vous permet de définir une condition de réussite - et la portée actuelle ne continuera à s'exécuter que si cette condition est remplie. Ils sont définis avec la syntaxe guard condition else {...}.

Donc, pour les utiliser avec une liaison facultative, vous pouvez le faire:

guard let number = anOptionalInt else {
    return
}

(Notez que dans le corps du garde, vous devez utiliser l'une des instructions de transfert de contrôle afin de quitter la portée du code en cours d'exécution).

Si anOptionalIntcontient une valeur, elle sera dépliée et affectée à la nouvelle numberconstante. Le code après le gardien continuera alors à s'exécuter. S'il ne contient pas de valeur - le gardien exécutera le code entre crochets, ce qui entraînera un transfert de contrôle, de sorte que le code immédiatement après ne sera pas exécuté.

La vraie chose intéressante à propos des instructions de garde est que la valeur non enveloppée est maintenant disponible à utiliser dans le code qui suit l'instruction (car nous savons que le futur code ne peut s'exécuter que si l'option a une valeur). C'est un excellent moyen d'éliminer les «pyramides de malheur» créées en imbriquant plusieurs instructions if.

Par exemple:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

Les gardes prennent également en charge les mêmes astuces que celles prises en charge par l'instruction if, telles que le déballage de plusieurs options en même temps et l'utilisation de la whereclause.

Que vous utilisiez une déclaration si ou la garde dépend entièrement de savoir si un code d'avenir nécessite l'option pour contenir une valeur.

Opérateur de coalescence nul

L' opérateur de coalescence nul est une version abrégée astucieuse de l' opérateur conditionnel ternaire , principalement conçu pour convertir les options en options. Il a la syntaxe a ?? b, où aest un type facultatif et best le même type que a(bien que généralement non facultatif).

Il vous permet essentiellement de dire «Si acontient une valeur, déballez-la. Si ce n'est pas le cas, revenez à la bplace ». Par exemple, vous pouvez l'utiliser comme ceci:

let number = anOptionalInt ?? 0

Cela définira une numberconstante de Inttype, qui contiendra la valeur de anOptionalInt, si elle contient une valeur, ou 0autrement.

C'est juste un raccourci pour:

let number = anOptionalInt != nil ? anOptionalInt! : 0

Chaînage en option

Vous pouvez utiliser le chaînage facultatif pour appeler une méthode ou accéder à une propriété sur un facultatif. Cela se fait simplement en suffixant le nom de la variable avec a ?lors de son utilisation.

Par exemple, disons que nous avons une variable foo, de type une Fooinstance facultative .

var foo : Foo?

Si nous voulions appeler une méthode fooqui ne retourne rien, nous pouvons simplement faire:

foo?.doSomethingInteresting()

Si foocontient une valeur, cette méthode sera appelée dessus. Si ce n'est pas le cas, rien de mauvais ne se produira - le code continuera simplement à s'exécuter.

(Il s'agit d'un comportement similaire à l'envoi de messages nildans Objective-C)

Cela peut donc également être utilisé pour définir des propriétés ainsi que des méthodes d'appel. Par exemple:

foo?.bar = Bar()

Encore une fois, de mauvais ne se passera rien ici si fooest nil. Votre code continuera simplement à s'exécuter.

Une autre astuce intéressante que le chaînage facultatif vous permet de faire est de vérifier si la définition d'une propriété ou l'appel d'une méthode a réussi. Vous pouvez le faire en comparant la valeur de retour à nil.

(Cela est dû au fait qu'une valeur facultative renvoie Void?plutôt qu'une Voidméthode qui ne renvoie rien)

Par exemple:

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

Cependant, les choses deviennent un peu plus délicates lorsque vous essayez d'accéder à des propriétés ou d'appeler des méthodes qui renvoient une valeur. Parce que fooc'est facultatif, tout ce qui en sera retourné sera également facultatif. Pour résoudre ce problème, vous pouvez soit déballer les options qui sont renvoyées à l'aide de l'une des méthodes ci-dessus - soit déballer foolui-même avant d'accéder aux méthodes ou d'appeler des méthodes qui renvoient des valeurs.

De plus, comme son nom l'indique, vous pouvez «enchaîner» ces déclarations ensemble. Cela signifie que si fooa une propriété facultative baz, qui a une propriété qux- vous pouvez écrire ce qui suit:

let optionalQux = foo?.baz?.qux

Encore une fois, car fooet bazsont facultatifs, la valeur renvoyée par quxsera toujours facultative, qu'elle quxsoit facultative.

map et flatMap

Une fonctionnalité souvent sous-utilisée avec les options est la possibilité d'utiliser les fonctions mapet flatMap. Ceux-ci vous permettent d'appliquer des transformations non facultatives aux variables facultatives. Si une option a une valeur, vous pouvez lui appliquer une transformation donnée. S'il n'a pas de valeur, il restera nil.

Par exemple, supposons que vous ayez une chaîne facultative:

let anOptionalString:String?

En lui appliquant la mapfonction - nous pouvons utiliser la stringByAppendingStringfonction afin de la concaténer à une autre chaîne.

Parce que stringByAppendingStringprend un argument de chaîne non facultatif, nous ne pouvons pas saisir directement notre chaîne facultative. Cependant, en utilisant map, nous pouvons utiliser allow stringByAppendingStringà utiliser si anOptionalStringa une valeur.

Par exemple:

var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")

Cependant, si anOptionalStringn'a pas de valeur, mapreviendra nil. Par exemple:

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMapfonctionne de manière similaire à map, sauf qu'il vous permet de renvoyer une autre option depuis le corps de fermeture. Cela signifie que vous pouvez saisir une option dans un processus qui nécessite une entrée non optionnelle, mais vous pouvez également générer une option elle-même.

try!

Le système de gestion des erreurs de Swift peut être utilisé en toute sécurité avec Do-Try-Catch :

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

Si someThrowingFunc()jette une erreur, l'erreur sera capturée en toute sécurité dans le catchbloc.

La errorconstante que vous voyez dans le catchbloc n'a pas été déclarée par nous - elle est automatiquement générée par catch.

Vous pouvez également errorvous déclarer , il a l'avantage de pouvoir le caster dans un format utile, par exemple:

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

L'utilisation de trycette façon est la bonne façon d'essayer, de détecter et de gérer les erreurs provenant des fonctions de lancement.

Il y a aussi try?qui absorbe l'erreur:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

Mais le système de gestion des erreurs de Swift fournit également un moyen de "forcer l'essai" avec try!:

let result = try! someThrowingFunc()

Les concepts expliqués dans cet article s'appliquent également ici: si une erreur est levée, l'application se bloque.

Vous ne devez l'utiliser que try!si vous pouvez prouver que son résultat n'échouera jamais dans votre contexte - et cela est très rare.

La plupart du temps, vous utiliserez le système Do-Try-Catch complet - et le système optionnel try?, dans les rares cas où la gestion de l'erreur n'est pas importante.


Ressources

Hamish
la source
87
Personnellement, je voudrais vous remercier d'avoir fait l'effort d'écrire tout cela. Je pense que cela sera certainement utile aux débutants et aux experts utilisant swift.
Pranav Wadhwa
15
Je suis content que vous l'ayez trouvé utile. Cette réponse est un wiki communautaire, c'est donc une collaboration entre de nombreuses personnes (7, jusqu'à présent)!
jtbandes
1
Cette erreur est intermittente dans mon cas. Aucune suggestion? Code dans stackoverflow.com/questions/50933681/…
py_ios_dev
1
Il est peut-être temps de mettre à jour cette réponse pour l'utiliser à la compactMap()place de flatMap().
Nicolas Miari
donc je suppose que la meilleure et courte solution est le "Nil Coalescing Operator", non?
mehdigriche
65

Réponse TL; DR

À quelques exceptions près , cette règle est d'or:

Évitez d'utiliser !

Déclarez la variable optional ( ?), et non les options implicitement non enveloppées (IUO) ( !)

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

Au lieu de:
var nameOfDaughter: String!

Déballez la variable facultative à l'aide de if letouguard let

Soit dérouler la variable comme ceci:

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

Ou comme ça:

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

Cette réponse était censée être concise, pour une compréhension complète, lire la réponse acceptée


Ressources

Sajjon
la source
40

Cette question revient tout le temps sur SO. C'est l'une des premières choses avec lesquelles les nouveaux développeurs Swift ont du mal.

Contexte:

Swift utilise le concept d '«options» pour traiter les valeurs qui pourraient contenir une valeur ou non. Dans d'autres langages comme C, vous pouvez stocker une valeur de 0 dans une variable pour indiquer qu'elle ne contient aucune valeur. Mais que faire 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 n'importe quel type pour contenir soit une valeur valide, soit aucune valeur.

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

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

Une option doit être "déballée" afin de récupérer la valeur à l'intérieur.

Le "!" L'opérateur est un opérateur "déplier de force". Il dit "faites-moi confiance. Je sais ce que je fais. Je garantis que lorsque ce code s'exécutera, la variable ne contiendra pas nil." Si vous vous trompez, vous vous plantez.

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

Comment gérer les options:

Il existe de nombreuses autres façons de gérer les options qui sont plus sûres. En voici quelques unes (liste non exhaustive)

Vous pouvez utiliser "liaison facultative" ou "si laissé" pour dire "si cette option contient une valeur, enregistrez cette valeur dans une nouvelle variable non facultative. Si l'option ne contient pas de valeur, ignorez le corps de cette instruction if ".

Voici un exemple de liaison optionnelle avec notre foooption:

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 le biding facultatif existe uniquement (est uniquement "dans la portée") dans le corps de l'instruction if.

Alternativement, vous pouvez utiliser une instruction guard, qui vous permet de quitter votre fonction si la variable est nulle:

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 préserver le «chemin d'or» dans votre code et d'éviter des niveaux toujours croissants de if imbriqués qui résultent parfois de l'utilisation de la liaison facultative «if let».

Il existe également une construction appelée "opérateur de coalescence nul". Il prend la forme "optionnelle_var ?? remplacement_val". Il renvoie une variable non facultative du même type que les données contenues dans l'option. Si l'option facultative contient nil, elle renvoie la valeur de l'expression après le "??" symbole.

Vous pouvez donc utiliser du code comme celui-ci:

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

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

ÉDITER:

Un autre piège légèrement plus subtil avec les options est "les options implicitement déballées. Lorsque nous déclarons foo, nous pouvons dire:

var foo: String!

Dans ce cas, foo est toujours facultatif, mais vous n'avez pas à le déballer pour le référencer. Cela signifie que chaque fois que vous essayez de référencer foo, vous 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éplier foo. l'impression semble bien, mais ce n'est pas le cas.

Vous devez donc être très prudent avec les options implicitement déballées. (et peut-être même les éviter complètement jusqu'à ce que vous ayez une bonne compréhension des options.)

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

Duncan C
la source
7
Vous devriez envisager d'en faire un wiki public.
vacawama
3
Modifiez votre réponse, et en dessous de la zone d'édition à droite se trouve une case wiki communautaire . Vous n'aurez plus de représentant pour la réponse, mais je sais que ce n'est pas un reproche flagrant de toute façon.
vacawama
6
Étant donné que cette question est posée un million de fois sur le site, si je devais prendre le temps de poster une question auto-répondue comme réponse canonique à la question, j'espère que la réponse est beaucoup plus complète que cela. Il n'y a rien sur les guardclauses. Il n'y a rien dans l'utilisation d' if varune construction aussi valide que if let. Il n'y a rien sur les whereclauses qui, à mon avis, mérite d'être mentionné lorsque nous parlons de if letliaison (cela supprime toute une couche d'imbrication dans de nombreux cas). Il n'y a rien à propos du chaînage optionnel.
nhgrif
6
Et lorsque vous mentionnez des options optionnelles implicitement dépliées, vous ne mentionnez même pas que vous pouvez utiliser toutes les astuces optionnelles susmentionnées pour dérouler en toute sécurité des options optionnelles implicitement non enveloppées. Nous ne couvrons pas non plus le déballage de plusieurs options dans la même clause.
nhgrif
1
@nhgrif, j'ai dit "Vous pouvez également utiliser la gestion des erreurs try / catch ou guard, mais généralement l'une des autres techniques ci-dessus est plus propre." Vous avez certainement de bons points. C'est un sujet assez vaste. Que diriez-vous de contribuer votre propre réponse qui couvre des choses que je n'ai pas plutôt que de faire des commentaires de sniping? De cette façon, la question et ses réponses deviennent un meilleur atout pour le site.
Duncan C
13

Étant donné que les réponses ci-dessus expliquent clairement comment jouer en toute sécurité avec les options. Je vais essayer d'expliquer ce que les options sont vraiment rapides.

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

var i : Optional<Int>

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

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

Donc, pour affecter un zéro à notre variable «i». On peut faire var i = Optional<Int>.none ou assigner une valeur, on va passer de la valeur var i = Optional<Int>.some(28)

Selon Swift, «néant» est l'absence de valeur. Et pour créer une instance initialisée avecnil Nous devons nous conformer à un protocole appelé ExpressibleByNilLiteralet génial si vous l'avez deviné, seulement se Optionalsconformer ExpressibleByNilLiteralet se conformer à d'autres types est déconseillé.

ExpressibleByNilLiterala une seule méthode appelée init(nilLiteral:)qui initialise une instance avec nil. Vous n'appelez généralement pas cette méthode et, selon la documentation rapide, il est déconseillé d'appeler cet initialiseur directement car le compilateur l'appelle chaque fois que vous initialisez un type facultatif avec un nillittéral.

Même moi, je dois envelopper (sans jeu de mots) ma tête autour des options : D Happy Swfting All .

Tharzeez
la source
12

Tout d'abord, vous devez savoir ce qu'est une valeur facultative. Vous pouvez accéder au langage de programmation Swift pour plus de 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 une valeur nulle. Donc, avant d'implémenter une valeur facultative, vous devez vérifier son état.

Vous pouvez utiliser if let ...ou guard let ... elseet ainsi de suite.

D'une autre manière, si vous ne souhaitez pas vérifier l'état de la variable avant votre implémentation, vous pouvez également l'utiliser à la var buildingName = buildingName ?? "buildingName"place.

QiunCheng
la source
8

J'ai eu cette erreur une fois lorsque j'essayais de définir mes valeurs de sortie à partir de la méthode de préparation pour la séquence 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 peux pas définir les valeurs des sorties du contrôleur de destination car le contrôleur n'a pas encore été chargé ou initialisé.

Je l'ai donc 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 n'importe qui là-bas avec le même problème que j'ai trouvé que la réponse marquée est une excellente ressource pour la compréhension des options et comment ils fonctionnent, mais n'a pas abordé le problème lui-même directement.

Wissa
la source
N'oubliez pas de mentionner où appeler updateView le contrôleur de destination;)
Ahmad F
@AhmadF bon point. Pourtant, appeler le updateViewdans le contrôleur de destination n'est pas nécessaire dans ce cas car j'utilise la namevariable pour définir le nameLabel.textdans leviewDidLoad . Mais si nous faisions beaucoup de configuration, il serait certainement préférable de créer une autre fonction et de l'appeler à partir de viewDidLoad.
Wissa
7

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

Il existe plusieurs scénarios qui conduisent à ce type d'erreur fatale:

  1. déballages forcés:

    let user = someVariable!

    Si someVariableest nul, alors vous obtiendrez un plantage. En effectuant un déballement forcé, vous avez déplacé la responsabilité de vérification nulle du compilateur vers vous, essentiellement en faisant un déballement forcé, vous garantissez au compilateur que vous n'aurez jamais de valeurs nulles là-bas. Et devinez ce qui se passe si en quelque sorte une valeur nulle se termine par someVariable?

    Solution? Utilisez la liaison facultative (alias if-let), faites le traitement des variables là-bas:

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

    let myRectangle = someShape as! Rectangle

    Ici, en forçant, vous dites au compilateur de ne plus vous inquiéter, car vous aurez toujours une Rectangleinstance là-bas. Et tant que cela tient, 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 facultative (alias if-let), faites le traitement des variables là-bas:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Options optionnelles implicitement déballé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
        }
    }

    Maintenant, si personne ne se trompe avec le name propriété en la définissant sur nil, cela fonctionne comme prévu, mais s'il Userest initialisé à partir d'un JSON qui n'a pas la nameclé, vous obtenez l'erreur fatale lorsque vous essayez d'utiliser la propriété.

    Solution? Ne les utilisez pas :) À moins que vous ne soyez sûr à 102% que la propriété aura toujours une valeur non nulle au moment où elle doit être utilisée. Dans la plupart des cas, la conversion en option ou non optionnelle fonctionnera. Si vous le rendez non facultatif, le compilateur vous aidera également en indiquant les chemins de code que vous avez manqués en donnant une valeur à cette propriété.

  4. Prises non connectées ou pas encore connectées. Il s'agit d'un cas particulier du scénario # 3. En gros, vous avez une classe chargée par XIB que vous souhaitez utiliser.

    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 voudrez 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 facultative, bien que dans ce cas, le compilateur vous obligera à la dérouler sur tout le code.

  5. Valeurs provenant d'Objective-C, et qui n'ont pas d'annotations de nullité. Supposons que nous avons la classe Objective-C suivante:

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

    Maintenant, si aucune annotation de nullité n'est spécifiée (explicitement ou via NS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END), la namepropriété sera importée dans Swift sous la forme String!(une IUO - implicitement déballée en option). Dès qu'un code rapide voudra utiliser la valeur, il se bloquera s'il nameest nul.

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

Cristik
la source
2

C'est plus un commentaire important et c'est pourquoi les options implicitement déballées peuvent être trompeuses quand il s'agit de déboguer nil valeurs.

Pensez au code suivant: Il se compile sans erreurs / avertissements:

c1.address.city = c3.address.city

Pourtant, lors de l'exécution, il donne l'erreur suivante: Erreur fatale: trouvé de manière inattendue nil lors du déballage d'une valeur facultative

Pouvez-vous me dire quel objet est nil ?

Tu ne peux pas!

Le code complet serait:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c3 = BadContact()

        c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct BadContact {
    var address : Address!
}

struct Address {
    var city : String
}

En bref, en utilisant, var address : Address!vous cachez la possibilité qu'une variable puisse provenir nild'autres lecteurs. Et quand ça tombe en panne, vous vous dites "qu'est-ce qui se passe ?!address n'est pas optionnel, alors pourquoi je plante?!.

Il vaut donc mieux écrire comme tel:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

Pouvez-vous maintenant me dire de quel objet il s'agissait nil?

Cette fois, le code vous a été rendu plus clair. Vous pouvez rationaliser et penser que c'est probablement le addressparamètre qui a été déplié avec force.

Le code complet serait:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c2 = GoodContact()

        c1.address.city = c2.address!.city
        c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
        c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
        if let city = c2.address?.city {  // safest approach. But that's not what I'm talking about here. 
            c1.address.city = city
        }

    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct GoodContact {
    var address : Address?
}

struct Address {
    var city : String
}
miel
la source
2

Les erreurs EXC_BAD_INSTRUCTIONet fatal error: unexpectedly found nil while implicitly unwrapping an Optional valueapparaissent le plus lorsque vous avez déclaré un @IBOutlet, mais pas connecté au storyboard .

Vous devriez également vous renseigner sur le fonctionnement des options , mentionné dans d'autres réponses, mais c'est la seule fois qui me semble le plus souvent.

Ale Mohamad
la source
la @IBOutletcause de cette erreur ne devrait-elle pas avoir l' erreur fatale: trouvé de manière inattendue nul alors que vous dépliez implicitement une version de valeur facultative de l'erreur?
pkamb
1
C'est vrai. Peut-être que lorsque j'envoie la réponse, c'est ce que je voulais dire et que je copie le premier message d'erreur fatal. La réponse de Hamish semble très complète à ce sujet.
Ale Mohamad
2

Si vous obtenez cette erreur dans CollectionView, essayez de créer également un fichier CustomCell et un fichier xib personnalisé.

ajoutez ce code dans ViewDidLoad () à mainVC.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
ShigaSuresh
la source
0

J'ai rencontré cette erreur lors de la transition d'un contrôleur de vue de table à un contrôleur de vue, car j'avais oublié de spécifier le nom de classe personnalisé pour le contrôleur de vue dans le storyboard principal.

Quelque chose de simple qui mérite d'être vérifié si tout le reste semble correct

Mike Volmar
la source