Pourquoi voudriez-vous créer un "Implicitly Unwrapped Optional" vs créer juste une variable régulière ou constante? Si vous savez qu'il peut être déroulé avec succès, pourquoi créer une option en premier lieu? Par exemple, pourquoi est-ce:
let someString: String! = "this is the string"
va être plus utile que:
let someString: String = "this is the string"
Si ”optionals indique qu'une constante ou une variable est autorisée à ne pas avoir de valeur”, mais “il est parfois clair, à partir de la structure d'un programme, qu'un optionnel aura toujours une valeur après que cette valeur a été définie la première fois”, quel est le point de en faire une option en premier lieu? Si vous savez qu'un facultatif aura toujours une valeur, cela ne le rend-il pas facultatif?
Prenons le cas d'un objet qui peut avoir des propriétés nulles lors de sa construction et de sa configuration, mais qui est immuable et non nul par la suite (NSImage est souvent traité de cette façon, même s'il est toujours utile de muter parfois). Les options implicitement non emballées nettoyeraient beaucoup son code, avec une perte de sécurité relativement faible (tant que la garantie est maintenue, elle est sûre).
(Éditer) Pour être clair cependant: les options ordinaires sont presque toujours préférables.
Avant de pouvoir décrire les cas d’utilisation pour les options implicitement non enveloppées, vous devez déjà comprendre quelles options et quels options sont implicitement disponibles dans Swift. Si vous ne le faites pas, je vous recommande de lire d'abord mon article sur les optionnels
Il y a deux raisons principales pour lesquelles on créerait une option implicitement non enveloppée. Tous ont à voir avec la définition d'une variable qui ne sera jamais accessible lorsque nil
car sinon, le compilateur Swift vous forcera toujours à décompresser explicitement un optionnel.
Chaque constante de membre doit avoir une valeur au moment où l'initialisation est terminée. Parfois, une constante ne peut pas être initialisée avec sa valeur correcte lors de l'initialisation, mais il est toujours possible de garantir qu'elle a une valeur avant son accès.
L'utilisation d'une variable facultative permet de contourner ce problème, car une propriété facultative est automatiquement initialisée avec nil
et la valeur qu'elle contiendra éventuellement sera toujours immuable. Cependant, il peut être pénible de constamment déployer une variable que vous savez sûrement n’est pas nulle. Les options non emballées implicitement procurent les mêmes avantages qu'une option, avec l'avantage supplémentaire qu'il n'est pas nécessaire de la dérouler explicitement partout.
Un bon exemple en est le cas où une variable membre ne peut pas être initialisée dans une sous-classe UIView tant que la vue n'est pas chargée:
class MyView: UIView {
@IBOutlet var button: UIButton!
var buttonOriginalWidth: CGFloat!
override func awakeFromNib() {
self.buttonOriginalWidth = self.button.frame.size.width
}
}
Ici, vous ne pouvez pas calculer la largeur d'origine du bouton avant le chargement de la vue, mais vous savez que awakeFromNib
sera appelé avant toute autre méthode de la vue (autre que l'initialisation). Au lieu de forcer la valeur à être explicitement explicitée sans enveloppe dans votre classe, vous pouvez la déclarer en tant qu'optimalement implicite.
nil
Cela devrait être extrêmement rare, mais si votre application ne peut pas continuer à fonctionner si une variable est nil
lors de son accès, ce serait une perte de temps de la tester à la place de nil
. Normalement, si vous avez une condition qui doit absolument être vraie pour que votre application continue à fonctionner, vous utiliserez un assert
. Une option implicitement non emballée est associée à une assertion pour nil. Même dans ce cas, il est souvent bon de déballer l’option facultative et d’utiliser une assertion plus descriptive si elle est nulle.
Parfois, vous avez une variable membre qui ne doit jamais être nulle, mais vous ne pouvez pas lui attribuer la valeur correcte lors de l'initialisation. Une solution consiste à utiliser une option implicitement décompressée, mais une meilleure solution consiste à utiliser une variable paresseuse:
class FileSystemItem {
}
class Directory : FileSystemItem {
lazy var contents : [FileSystemItem] = {
var loadedContents = [FileSystemItem]()
// load contents and append to loadedContents
return loadedContents
}()
}
Désormais, la variable membre contents
n'est pas initialisée avant le premier accès. Cela donne à la classe une chance d'entrer dans le bon état avant de calculer la valeur initiale.
Note: Cela peut sembler contredire le # 1 d'en haut. Cependant, il y a une distinction importante à faire. La buttonOriginalWidth
ci-dessus doit être définie pendant viewDidLoad afin d'empêcher quiconque de modifier la largeur des boutons avant l'accès à la propriété.
Dans la plupart des cas, vous devez éviter les options implicites non emballées, car si vous l'utilisiez par erreur, votre application entière se bloquerait si vous y accédez par nil
. Si vous ne savez pas toujours si une variable peut être nulle, utilisez toujours une valeur Facultative normale. Déballer une variable qui n'est jamais nil
ne fait certainement pas très mal.
Les options facultatives implicitement non enveloppées sont utiles pour présenter une propriété comme non optionnelle alors qu'elle doit être optionnelle sous les couvertures. Cela est souvent nécessaire pour "attacher le noeud" entre deux objets liés qui ont besoin d'une référence à l'autre. Cela a du sens quand aucune des références n'est en fait optionnel, mais l'une d'entre elles doit être nulle pendant l'initialisation de la paire.
Par exemple:
// These classes are buddies that never go anywhere without each other
class B {
var name : String
weak var myBuddyA : A!
init(name : String) {
self.name = name
}
}
class A {
var name : String
var myBuddyB : B
init(name : String) {
self.name = name
myBuddyB = B(name:"\(name)'s buddy B")
myBuddyB.myBuddyA = self
}
}
var a = A(name:"Big A")
println(a.myBuddyB.name) // prints "Big A's buddy B"
Toute instance B
doit toujours avoir une référence myBuddyA
valide. Par conséquent, nous ne voulons pas que l'utilisateur la considère comme facultative, mais nous avons besoin qu'elle soit facultative pour pouvoir construire un B
avant que nous ayons un A
à référencer.
POURTANT! Ce type d'exigence de référence mutuelle est souvent une indication d'un couplage étroit et d'une conception médiocre. Si vous vous basez sur des options implicitement non enveloppées, vous devriez probablement envisager un refactoring pour éliminer les dépendances croisées.
Les options implicitement non enveloppées sont un compromis pragmatique pour rendre le travail dans un environnement hybride qui doit interagir avec les frameworks Cocoa existants et leurs conventions, tout en permettant une migration progressive dans un paradigme de programmation plus sûr - sans pointeurs nuls - imposé par le Swift compilateur.
Swift book, chapitre - Les bases , section Options implicitement non emballées dit:
Les options facultatives implicitement non enveloppées sont utiles lorsqu'il est confirmé que la valeur d'une option existe immédiatement après sa définition initiale et que l'on peut donc supposer qu'elle existe à chaque instant par la suite. Les options optionnelles non enveloppées dans Swift sont principalement utilisées lors de l'initialisation de la classe, comme décrit dans la section Références non possédées et propriétés facultatives implicitement non enveloppées .
…
Vous pouvez imaginer un élément facultatif implicitement non enveloppé comme donnant l’autorisation que cet élément facultatif soit automatiquement déballé chaque fois qu’il est utilisé. Plutôt que de placer un point d'exclamation après le nom de l'option chaque fois que vous l'utilisez, vous placez un point d'exclamation après le type de l'option lorsque vous le déclarez.
Cela revient à des cas d'utilisation où la non -nil
- ness des propriétés est établie via une convention d'utilisation et ne peut pas être appliquée par le compilateur. lors de l'initialisation de la classe. Par exemple, les propriétés UIViewController
initialisées à partir de NIB ou de Storyboards, où l'initialisation est divisée en phases distinctes, mais après la viewDidLoad()
, vous pouvez supposer que les propriétés existent généralement. Sinon, pour satisfaire le compilateur, vous deviez utiliser le décompression forcée , liaison facultative ou chaînage facultatif uniquement pour masquer l'objectif principal du code.
La partie ci-dessus du livre Swift fait également référence au chapitre Comptage automatique de références :
Cependant, il existe un troisième scénario dans lequel les deux propriétés devraient toujours avoir une valeur et aucune propriété ne devrait jamais être
nil
une fois l'initialisation terminée. Dans ce scénario, il est utile de combiner une propriété non possédée sur une classe avec une propriété facultative implicitement non enveloppée sur l'autre classe.Cela permet d'accéder directement aux deux propriétés (sans décompression facultative) une fois l'initialisation terminée, tout en évitant un cycle de référence.
Cela revient aux bizarreries de ne pas être un langage mal ordonné, donc la rupture des cycles de rétention est sur vous en tant que programmeur et les options implicites non enveloppées sont un outil pour masquer cette bizarrerie.
Cela couvre la question "Quand utiliser des options implicitement non enveloppées dans votre code?". En tant que développeur d’applications, vous les rencontrez principalement dans les signatures de méthodes de bibliothèques écrites en Objective-C, qui n’a pas la capacité d’exprimer des types facultatifs.
De tilisation de Swift avec Cocoa et Objective-C, section Utilisation de nil :
Objective-C ne garantissant pas qu'un objet est non nul, Swift rend toutes les classes des types d'argument et les types de retour facultatifs dans les API Objective-C importées. Avant d'utiliser un objet Objective-C, vous devez vérifier qu'il ne manque pas.
Dans certains cas, vous pouvez absolument savoir qu'une méthode ou une propriété Objective-C ne renvoie jamais une référence d'objet
nil
. Pour faciliter l'utilisation des objets de ce scénario spécial, Swift importe les types d'objet sous la forme des options implicitement non enveloppées . Les types facultatifs implicitement non emballés incluent toutes les fonctionnalités de sécurité des types facultatifs. De plus, vous pouvez accéder à la valeur directement sans vérifiernil
ni la dérouler vous-même. Lorsque vous accédez à la valeur dans ce type de type facultatif sans la dérouler en toute sécurité au préalable, l'option facultative implicitement décodée vérifie si la valeur est manquante. Si la valeur est manquante, une erreur d'exécution se produit. Par conséquent, vous devez toujours vérifier et dérouler vous-même une option implicitement non emballée, à moins que vous ne soyez sûr que la valeur ne peut pas être manquante.
... et plus loin ici
Les exemples simples à une ligne (ou à plusieurs lignes) ne couvrent pas très bien le comportement des optionnels - oui, si vous déclarez une variable et lui donnez une valeur tout de suite, rien ne sert de facultatif.
Le meilleur des cas que j’ai vu jusqu’à présent est la configuration qui survient après l’initialisation de l’objet, suivie de l’utilisation "garantie" de suivre cette configuration, par exemple. dans un contrôleur de vue:
class MyViewController: UIViewController {
var screenSize: CGSize?
override func viewDidLoad {
super.viewDidLoad()
screenSize = view.frame.size
}
@IBAction printSize(sender: UIButton) {
println("Screen size: \(screenSize!)")
}
}
Nous savons que printSize
sera appelé une fois la vue chargée - il s'agit d'une méthode d'action reliée à un contrôle situé dans cette vue, et nous nous sommes assurés de ne pas l'appeler autrement. Nous pouvons donc nous épargner quelques vérifications/liaisons optionnelles avec le !
. Swift ne reconnaît pas cette garantie (du moins jusqu'à ce que Apple résolve le problème d'arrêt), de sorte que vous en informiez le compilateur.
Cela casse toutefois la sécurité du type dans une certaine mesure. Quel que soit l'endroit où vous avez une option implicitement non emballée, votre application peut se bloquer si votre "garantie" ne tient pas toujours, c'est donc une fonctionnalité à utiliser avec parcimonie. De plus, l'utilisation de !
tout le temps donne l'impression que vous criez et personne n'aime ça.
Apple donne un excellent exemple dans Le Swift Langage de programmation -> Comptage automatique de références -> Résolution des cycles de référence puissants entre les instances de classe -> Références non possédées et propriétés facultatives implicitement non emballées
class Country {
let name: String
var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
L'initialiseur pour
City
est appelé depuis l'initialiseur pourCountry
. Cependant, l'initialiseur pourCountry
ne peut pas transmettreself
à l'initialiseurCity
tant qu'une nouvelle instanceCountry
n'est pas complètement initialisée, comme décrit dans Initialisation .Pour faire face à cette exigence, vous déclarez la propriété
capitalCity
deCountry
en tant que propriété facultative implicitement non enveloppée.
Si vous êtes certain, une valeur renvoyée par un optionnel au lieu de nil
, Optionals implicitement non enveloppés permet d’attraper directement ces valeurs d’options et les non optionnels ne le peuvent pas.
//Optional string with a value
let optionalString: String? = "This is an optional String"
//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!
//Declaration of a non Optional String
let nonOptionalString: String
//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString
//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString
C'est donc la différence entre l'utilisation de
let someString : String!
etlet someString : String
La justification des options implicites est plus facile à expliquer en examinant d’abord la justification du déploiement forcé.
Déballage forcé d'une option (implicite ou non), en utilisant le! opérateur, cela signifie que vous êtes certain que votre code ne contient pas de bogues et que l’option (optionnel) a déjà une valeur telle qu’elle est déballée. Sans le ! opérateur, vous diriez probablement simplement avec une liaison optionnelle:
if let value = optionalWhichTotallyHasAValue {
println("\(value)")
} else {
assert(false)
}
qui n'est pas aussi gentil que
println("\(value!)")
Désormais, les options implicites vous permettent d’exprimer une option dont vous attendez toujours avoir une valeur lorsqu’elle est déballée, dans tous les flux possibles. Cela va donc encore plus loin pour vous aider - en allégeant l'obligation d'écrire le! à déballer à chaque fois, et en veillant à ce que le moteur d'exécution continue à se tromper au cas où vos hypothèses sur le flux seraient fausses.
Je pense que Optional
est un mauvais nom pour cette construction qui confond beaucoup de débutants.
D'autres langues (Kotlin et C # par exemple) utilisent le terme Nullable
, ce qui facilite beaucoup la compréhension.
Nullable
signifie que vous pouvez affecter une valeur null à une variable de ce type. Donc, si c'est Nullable<SomeClassType>
, vous pouvez lui affecter des valeurs nulles, s'il ne s'agit que de SomeClassType
, vous ne pouvez pas. C'est comme ça que Swift fonctionne.
Pourquoi les utiliser? Eh bien, parfois, vous devez avoir des valeurs nulles, c'est pourquoi. Par exemple, lorsque vous savez que vous voulez avoir un champ dans une classe, mais que vous ne pouvez rien affecter lorsque vous créez une instance de cette classe, vous le ferez plus tard. Je ne donnerai pas d'exemples, car les gens les ont déjà fournis ici. J'écris juste ceci pour donner mes 2 centimes.
Au fait, je vous suggère de regarder comment cela fonctionne dans d'autres langages, comme Kotlin et C #.
Voici un lien expliquant cette fonctionnalité dans Kotlin: https://kotlinlang.org/docs/reference/null-safety.html
D'autres langues, comme Java et Scala ont Optional
s, mais fonctionnent différemment de Optional
s dans Swift, car Java et Scala les types sont tous nuls par défaut.
Globalement, je pense que cette fonctionnalité aurait dû être nommée Nullable
dans Swift, et non pas Optional
...