Une option dans Swift est un type qui peut contenir une valeur ou aucune valeur. Les options sont écrites en ajoutant un ?
à n'importe quel type:
var name: String? = "Bertie"
Les options (avec les génériques) sont l'un des concepts Swift les plus difficiles à comprendre. En raison de la façon dont ils sont écrits et utilisés, il est facile de se faire une mauvaise idée de ce qu'ils sont. Comparez l'option facultative ci-dessus à la création d'une chaîne normale:
var name: String = "Bertie" // No "?" after String
D'après la syntaxe, il semble qu'une chaîne facultative soit très similaire à une chaîne ordinaire. Ce n'est pas. Une chaîne facultative n'est pas une chaîne avec un paramètre "facultatif" activé. Ce n'est pas une variété spéciale de String. Une chaîne et une chaîne facultative sont des types complètement différents.
Voici la chose la plus importante à savoir: une option est une sorte de conteneur. Une chaîne facultative est un conteneur qui peut contenir une chaîne. Un Int facultatif est un conteneur qui peut contenir un Int. Considérez une option comme une sorte de colis. Avant de l'ouvrir (ou "dérouler" dans la langue des options), vous ne saurez pas s'il contient quelque chose ou rien.
Vous pouvez voir comment les options sont implémentées dans la bibliothèque standard de Swift en tapant "Facultatif" dans n'importe quel fichier Swift et en cliquant dessus. Voici la partie importante de la définition:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Facultatif est juste un enum
qui peut être l'un des deux cas: .none
ou .some
. Si c'est le cas .some
, il y a une valeur associée qui, dans l'exemple ci-dessus, serait le String
"Bonjour". Une option utilise Generics pour donner un type à la valeur associée. Le type d'une chaîne facultative ne l'est pas String
, c'est Optional
ou plus précisément Optional<String>
.
Tout ce que Swift fait avec les options est magique pour rendre la lecture et l'écriture de code plus fluide. Malheureusement, cela obscurcit la façon dont cela fonctionne réellement. Je vais passer en revue certaines des astuces plus tard.
Remarque: je vais beaucoup parler des variables facultatives, mais c'est bien de créer des constantes facultatives aussi. Je marque toutes les variables avec leur type pour faciliter la compréhension des types de types en cours de création, mais vous n'avez pas à le faire dans votre propre code.
Comment créer des options
Pour créer une option, ajoutez un ?
après le type que vous souhaitez encapsuler. Tout type peut être facultatif, même vos propres types personnalisés. Vous ne pouvez pas avoir d'espace entre le type et le ?
.
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
Utilisation des options
Vous pouvez comparer un facultatif pour nil
voir s'il a une valeur:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
C'est un peu déroutant. Cela implique qu'une option est soit une chose, soit une autre. C'est nul ou c'est "Bob". Ce n'est pas vrai, le facultatif ne se transforme pas en quelque chose d'autre. Le comparer à nil est une astuce pour rendre le code plus facile à lire. Si un facultatif est égal à zéro, cela signifie simplement que l'énumération est actuellement définie sur .none
.
Seuls les optionnels peuvent être nuls
Si vous essayez de définir une variable non facultative sur nil, vous obtiendrez une erreur.
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
Une autre façon de voir les options est en complément des variables Swift normales. Ils sont la contrepartie d'une variable dont la valeur est garantie. Swift est un langage prudent qui déteste l'ambiguïté. La plupart des variables sont définies comme non optionnelles, mais parfois ce n'est pas possible. Par exemple, imaginez un contrôleur de vue qui charge une image à partir d'un cache ou du réseau. Il peut ou non avoir cette image au moment de la création du contrôleur de vue. Il n'y a aucun moyen de garantir la valeur de la variable image. Dans ce cas, vous devrez le rendre facultatif. Il commence au fur nil
et à mesure que l'image est récupérée, le facultatif obtient une valeur.
L'utilisation d'une option révèle l'intention du programmeur. Par rapport à Objective-C, où n'importe quel objet peut être nul, Swift a besoin que vous sachiez clairement quand une valeur peut être manquante et quand elle est garantie d'exister.
Pour utiliser une option, vous la "déballez"
Une option String
ne peut pas être utilisée à la place d'une réelle String
. Pour utiliser la valeur encapsulée dans une option, vous devez la déballer. Le moyen le plus simple de déballer une option est d'ajouter un !
après le nom facultatif. C'est ce qu'on appelle le "déballage forcé". Il renvoie la valeur à l'intérieur de l'option (comme le type d'origine) mais si l'option est nil
, elle provoque un crash d'exécution. Avant de déballer, vous devez vous assurer qu'il y a une valeur.
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
Vérification et utilisation d'une option
Parce que vous devez toujours vérifier zéro avant de déballer et d'utiliser une option, c'est un modèle courant:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
Dans ce modèle, vous vérifiez qu'une valeur est présente, puis lorsque vous êtes sûr qu'elle l'est, vous forcez à la dérouler dans une constante temporaire à utiliser. Parce que c'est une chose courante à faire, Swift propose un raccourci en utilisant "if let". C'est ce qu'on appelle la "liaison facultative".
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
Cela crée une constante temporaire (ou variable si vous remplacez let
par var
) dont la portée se trouve uniquement entre les accolades de l'if. Parce que devoir utiliser un nom tel que "unwrappedMealPreference" ou "realMealPreference" est un fardeau, Swift vous permet de réutiliser le nom de variable d'origine, en créant un nom temporaire dans la portée du support
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
Voici du code pour démontrer qu'une variable différente est utilisée:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
La liaison facultative fonctionne en vérifiant si l'option est égale à zéro. Si ce n'est pas le cas, il décompresse l'option dans la constante fournie et exécute le bloc. Dans Xcode 8.3 et versions ultérieures (Swift 3.1), essayer d'imprimer une option comme celle-ci entraînera un avertissement inutile. Utilisez les options debugDescription
pour le désactiver:
print("\(mealPreference.debugDescription)")
À quoi servent les options?
Les options ont deux cas d'utilisation:
- Des choses qui peuvent échouer (je m'attendais à quelque chose mais je n'ai rien eu)
- Des choses qui ne sont rien maintenant mais qui pourraient être quelque chose plus tard (et vice-versa)
Quelques exemples concrets:
- Une propriété qui peut être là ou pas là, comme
middleName
ou spouse
dans une Person
classe
- Une méthode qui peut retourner une valeur ou rien, comme la recherche d'une correspondance dans un tableau
- Une méthode qui peut renvoyer un résultat ou obtenir une erreur et ne rien retourner, comme essayer de lire le contenu d'un fichier (qui renvoie normalement les données du fichier) mais le fichier n'existe pas
- Propriétés de délégué, qui ne doivent pas toujours être définies et sont généralement définies après l'initialisation
- Pour les
weak
propriétés dans les classes. La chose sur laquelle ils pointent peut être réglée nil
à tout moment
- Une grande ressource qui pourrait devoir être libérée pour récupérer de la mémoire
- Lorsque vous avez besoin d'un moyen de savoir quand une valeur a été définie (données non encore chargées> les données) au lieu d'utiliser des données distinctes
Boolean
Les optionnels n'existent pas dans Objective-C mais il existe un concept équivalent, renvoyant nil. Les méthodes qui peuvent renvoyer un objet peuvent retourner nil à la place. Cela signifie "l'absence d'un objet valide" et est souvent utilisé pour dire que quelque chose s'est mal passé. Il ne fonctionne qu'avec des objets Objective-C, pas avec des primitives ou des types C de base (énumérations, structures). Objective-C avait souvent des types spécialisés pour représenter l'absence de ces valeurs ( NSNotFound
ce qui est vraiment NSIntegerMax
, kCLLocationCoordinate2DInvalid
pour représenter une coordonnée invalide, -1
ou une valeur négative est également utilisée). Le codeur doit connaître ces valeurs spéciales, elles doivent donc être documentées et apprises pour chaque cas. Si une méthode ne peut pas prendre nil
comme paramètre, cela doit être documenté. Dans Objective-C,nil
était un pointeur tout comme tous les objets étaient définis comme des pointeurs, mais nil
pointait vers une adresse spécifique (zéro). Dans Swift, nil
est un littéral qui signifie l'absence d'un certain type.
Comparé à nil
Vous pouviez utiliser n'importe quelle option en tant que Boolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
Dans les versions plus récentes de Swift, vous devez utiliser leatherTrim != nil
. Pourquoi est-ce? Le problème est qu'un Boolean
peut être enveloppé dans une option. Si vous avez Boolean
comme ça:
var ambiguous: Boolean? = false
il a deux sortes de "faux", un où il n'y a pas de valeur et un où il a une valeur mais la valeur est false
. Swift déteste l'ambiguïté, vous devez donc toujours vérifier une option nil
.
Vous vous demandez peut-être à quoi Boolean
sert une option ? Comme pour les autres options, l' .none
état pourrait indiquer que la valeur est encore inconnue. Il peut y avoir quelque chose à l'autre bout d'un appel réseau qui prend un certain temps à interroger. Les booléens facultatifs sont également appelés « booléens à trois valeurs »
Astuces rapides
Swift utilise quelques astuces pour permettre aux options de fonctionner. Considérez ces trois lignes de code optionnel ordinaire;
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
Aucune de ces lignes ne doit être compilée.
- La première ligne définit une chaîne facultative à l'aide d'un littéral chaîne, deux types différents. Même si c'était un,
String
les types sont différents
- La deuxième ligne définit une chaîne facultative sur nil, deux types différents
- La troisième ligne compare une chaîne facultative à nil, deux types différents
Je vais passer en revue certains des détails d'implémentation des options qui permettent à ces lignes de fonctionner.
Création d'une option
Utiliser ?
pour créer une option est du sucre syntaxique, activé par le compilateur Swift. Si vous voulez le faire à long terme, vous pouvez créer une option comme celle-ci:
var name: Optional<String> = Optional("Bob")
Cela appelle Optional
le premier initialiseur de, public init(_ some: Wrapped)
qui déduit le type associé facultatif à partir du type utilisé entre parenthèses.
La façon encore plus longue de créer et de définir une option:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
Définir une option sur nil
Vous pouvez créer une option sans valeur initiale ou en créer une avec la valeur initiale de nil
(les deux ont le même résultat).
var name: String?
var name: String? = nil
Permettre aux optionnels d'être égaux nil
est activé par le protocole ExpressibleByNilLiteral
(précédemment nommé NilLiteralConvertible
). L'option est créé avec la Optional
deuxième initialiseur de », public init(nilLiteral: ())
. Les docs disent que vous ne devriez pas utiliser ExpressibleByNilLiteral
quoi que ce soit, sauf les options, car cela changerait la signification de nil dans votre code, mais il est possible de le faire:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
Le même protocole vous permet de définir un optionnel déjà créé sur nil
. Bien que ce ne soit pas recommandé, vous pouvez utiliser directement l'initialiseur littéral nil:
var name: Optional<String> = Optional(nilLiteral: ())
Comparaison d'une option à nil
Les options définissent deux opérateurs spéciaux "==" et "! =", Que vous pouvez voir dans la Optional
définition. La première ==
vous permet de vérifier si une option est égale à zéro. Deux options différentes qui sont définies sur .none seront toujours égales si les types associés sont les mêmes. Lorsque vous comparez à zéro, Swift crée en arrière-plan une option du même type associé, définie sur .none, puis l'utilise pour la comparaison.
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
Le deuxième ==
opérateur vous permet de comparer deux options. Les deux doivent être du même type et ce type doit être conforme Equatable
(le protocole qui permet de comparer les choses avec l'opérateur régulier "=="). Swift (vraisemblablement) déballe les deux valeurs et les compare directement. Il gère également le cas où l'un ou les deux options sont .none
. Notez la distinction entre comparer au nil
littéral.
De plus, il vous permet de comparer n'importe quel Equatable
type à un habillage optionnel de ce type:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
Dans les coulisses, Swift encapsule le non-optionnel comme optionnel avant la comparaison. Cela fonctionne aussi avec les littéraux ( if 23 == numberFromString {
)
J'ai dit qu'il y a deux ==
opérateurs, mais il y a en fait un troisième qui vous permet de mettre nil
sur le côté gauche de la comparaison
if nil == name { ... }
Options de dénomination
Il n'y a pas de convention Swift pour nommer les types facultatifs différemment des types non facultatifs. Les gens évitent d'ajouter quelque chose au nom pour montrer qu'il s'agit d'un type facultatif (comme "optionalMiddleName" ou "possibleNumberAsString") et laisser la déclaration montrer qu'il s'agit d'un type facultatif. Cela devient difficile lorsque vous voulez nommer quelque chose pour contenir la valeur d'une option. Le nom "middleName" implique qu'il s'agit d'un type String, donc lorsque vous en extrayez la valeur, vous pouvez souvent vous retrouver avec des noms comme "actualMiddleName" ou "unwrappedMiddleName" ou "realMiddleName". Utilisez la liaison facultative et réutilisez le nom de la variable pour contourner ce problème.
La définition officielle
Extrait de "The Basics" dans le langage de programmation Swift :
Swift introduit également des types facultatifs, qui gèrent l'absence de valeur. Les optionnels disent «il y a une valeur, et elle est égale à x» ou «il n'y a pas du tout de valeur». Les options sont similaires à l'utilisation de nil avec des pointeurs dans Objective-C, mais elles fonctionnent pour tout type, pas seulement pour les classes. Les options sont plus sûres et plus expressives que les pointeurs nuls dans Objective-C et sont au cœur de nombreuses fonctionnalités les plus puissantes de Swift.
Les options sont un exemple du fait que Swift est un langage sûr de type. Swift vous aide à être clair sur les types de valeurs avec lesquelles votre code peut fonctionner. 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. Cela vous permet de détecter et de corriger les erreurs le plus tôt possible dans le processus de développement.
Pour terminer, voici un poème de 1899 sur les options:
Hier, dans l'escalier,
j'ai rencontré un homme qui n'était pas là
Il n'était plus là aujourd'hui
Je souhaite, je souhaite qu'il s'en aille
Antigonish
Plus de ressources: