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, Optional
est 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 nil
n'est pas la même chose que nil
dans Objective-C. Dans Objective-C, nil
est 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.
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 anOptionalString
quelnil
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 nil
au 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 anOptionalInt
contient une valeur, elle sera dépliée et affectée à la nouvelle number
constante. 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 where
clause.
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ù a
est un type facultatif et b
est le même type que a
(bien que généralement non facultatif).
Il vous permet essentiellement de dire «Si a
contient une valeur, déballez-la. Si ce n'est pas le cas, revenez à la b
place ». Par exemple, vous pouvez l'utiliser comme ceci:
let number = anOptionalInt ?? 0
Cela définira une number
constante de Int
type, qui contiendra la valeur de anOptionalInt
, si elle contient une valeur, ou 0
autrement.
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 Foo
instance facultative .
var foo : Foo?
Si nous voulions appeler une méthode foo
qui ne retourne rien, nous pouvons simplement faire:
foo?.doSomethingInteresting()
Si foo
contient 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 nil
dans 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 foo
est 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 Void
mé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 foo
c'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 foo
lui-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 foo
a 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 foo
et baz
sont facultatifs, la valeur renvoyée par qux
sera toujours facultative, qu'elle qux
soit facultative.
map
et flatMap
Une fonctionnalité souvent sous-utilisée avec les options est la possibilité d'utiliser les fonctions map
et 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 map
fonction - nous pouvons utiliser la stringByAppendingString
fonction afin de la concaténer à une autre chaîne.
Parce que stringByAppendingString
prend 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 anOptionalString
a une valeur.
Par exemple:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Cependant, si anOptionalString
n'a pas de valeur, map
reviendra nil
. Par exemple:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
fonctionne 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 catch
bloc.
La error
constante que vous voyez dans le catch
bloc n'a pas été déclarée par nous - elle est automatiquement générée par catch
.
Vous pouvez également error
vous 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 try
cette 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