Qu'est-ce qu'une «valeur non emballée» dans Swift?

155

J'apprends Swift pour iOS 8 / OSX 10.10 en suivant ce tutoriel , et le terme « valeur non emballée » est utilisé plusieurs fois, comme dans ce paragraphe (sous Objets et classe ):

Lorsque vous travaillez avec des valeurs facultatives, vous pouvez écrire? avant les opérations telles que les méthodes, les propriétés et les indices. Si la valeur avant le? est nul, tout après le? est ignoré et la valeur de l'expression entière est nulle. Sinon, la valeur facultative est déroulée , et tout après le? agit sur la valeur déballée . Dans les deux cas, la valeur de l'expression entière est une valeur facultative.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

Je ne comprends pas et j'ai cherché sur le Web sans succès.

Que cela veut-il dire?


Éditer

D'après la réponse de Cezary, il y a une légère différence entre la sortie du code d'origine et la solution finale (testée sur terrain de jeu):

Code d'origine

Code d'origine

La solution de Cezary

La solution de Cezary

Les propriétés de la superclasse sont affichées dans la sortie dans le second cas, alors qu'il y a un objet vide dans le premier cas.

Le résultat n'est-il pas censé être identique dans les deux cas?

Questions et réponses associées: Qu'est-ce qu'une valeur facultative dans Swift?

Bigood
la source
1
La réponse de Cezary est parfaite. De plus, il y a un excellent livre d'introduction à Swift disponible GRATUITEMENT sur iBooks
Daniel Storm
@DanielStormApps Cet iBook est une version portable du tutoriel lié à ma question :)
Bigood

Réponses:

289

Tout d'abord, vous devez comprendre ce qu'est un type facultatif. Un type optionnel signifie essentiellement que la variable peut être nil .

Exemple:

var canBeNil : Int? = 4
canBeNil = nil

Le point d'interrogation indique le fait que cela canBeNilpeut être nil.

Cela ne fonctionnerait pas:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

Pour obtenir la valeur de votre variable si elle est facultative, vous devez la dérouler . Cela signifie simplement mettre un point d'exclamation à la fin.

var canBeNil : Int? = 4
println(canBeNil!)

Votre code doit ressembler à ceci:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Une remarque:

Vous pouvez également déclarer des options à dérouler automatiquement en utilisant un point d'exclamation au lieu d'un point d'interrogation.

Exemple:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

Une autre façon de corriger votre code est donc:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

ÉDITER:

La différence que vous voyez est exactement le symptôme du fait que la valeur facultative est encapsulée . Il y a une autre couche en plus. La version non emballée montre simplement l'objet droit parce qu'il est, eh bien, déballé.

Une comparaison rapide des terrains de jeux:

terrain de jeux

Dans le premier et le second cas, l'objet n'est pas automatiquement déballé, donc vous voyez deux "calques" ( {{...}}), alors que dans le troisième cas, vous ne voyez qu'un calque ( {...}) car l'objet est automatiquement déballé.

La différence entre le premier cas et les deux seconds cas est que les deux seconds cas vous donneront une erreur d'exécution si optionalSquareest défini sur nil. En utilisant la syntaxe dans le premier cas, vous pouvez faire quelque chose comme ceci:

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}
Cezary Wojcik
la source
1
Merci pour l'explication claire! Le code inclus dans ma question est cité du tutoriel d'Apple (lien dans la question), et je suppose qu'il est censé fonctionner tel quel (et c'est le cas). Cependant, votre solution finale et celle d'origine produisent des résultats différents (voir ma modification). Toute pensée?
Bigood le
2
Cool. Excellente explication. Je suis toujours confus, cependant. Pourquoi voudrais-je utiliser une valeur facultative? Quand est-ce utile? Pourquoi Apple a-t-il créé ce comportement et cette syntaxe?
Todd Lehman
1
C'est particulièrement pertinent en ce qui concerne les propriétés de classe. Swift nécessite que toutes les méthodes non facultatives soient initialisées dans la init()fonction de la classe. Je recommande de lire les chapitres "The Basics" (couvre les options), "Initialization" et "Optional Chaining" dans le livre Swift publié par Apple.
Cezary Wojcik
2
@ToddLehman Apple a créé ceci pour vous aider à écrire un code beaucoup plus sûr. Par exemple, imaginez que toutes les exceptions de pointeur nul en Java plantent un programme parce que quelqu'un a oublié de vérifier la valeur null. Ou un comportement étrange sur Objective-C parce que vous avez oublié de vérifier zéro. Avec le type facultatif, vous ne pouvez pas oublier de traiter nil. Le compilateur vous oblige à y faire face.
Erik Engheim
5
@AdamSmith - Venant d'un arrière-plan Java, je peux certainement voir l'utilisation des options ... mais venant aussi (plus récemment) d'un arrière-plan Objective-C, j'ai toujours du mal à en voir l'utilisation, car Objective- C vous permet explicitement d'envoyer des messages à des objets nil. Hmm. J'aimerais voir quelqu'un écrire un exemple de ceux-ci montrant comment ils contribuent à rendre le code plus court et plus sûr que le code équivalent qui ne les utilise pas. Je ne dis pas qu'ils ne sont pas utiles; Je dis juste que je ne «comprends pas» encore.
Todd Lehman
17

La bonne réponse existante est excellente, mais j'ai trouvé que pour comprendre cela pleinement, j'avais besoin d'une bonne analogie, car il s'agit d'un concept très abstrait et étrange.

Alors, permettez-moi d'aider ces collègues développeurs «au cerveau droit» (pensée visuelle) en leur donnant une perspective différente en plus de la bonne réponse. Voici une bonne analogie qui m'a beaucoup aidé.

Analogie de l'emballage du cadeau d'anniversaire

Considérez les options comme des cadeaux d'anniversaire qui se présentent dans un emballage rigide, dur et coloré.

Vous ne savez pas s'il y a quelque chose à l'intérieur de l'emballage jusqu'à ce que vous déballiez le cadeau - peut-être qu'il n'y a rien du tout à l'intérieur! S'il y a quelque chose à l'intérieur, ce pourrait être encore un autre cadeau, qui est également enveloppé, et qui peut également ne contenir rien . Vous pourriez même déballer 100 cadeaux imbriqués pour enfin découvrir qu'il n'y avait rien d'autre que l'emballage .

Si la valeur de l'option optionnelle n'est pas nil, vous avez maintenant révélé une boîte contenant quelque chose . Mais, surtout si la valeur n'est pas explicitement typée et est une variable et non une constante prédéfinie, vous devrez peut-être ouvrir la boîte avant de pouvoir savoir quoi que ce soit de spécifique sur ce qu'il y a dans la boîte, comme de quel type il s'agit ou de quoi il s'agit. la valeur réelle est.

Qu'y a-t-il dans la boite?! Analogie

Même après avoir déballé la variable, vous êtes toujours comme Brad Pitt dans la dernière scène de SE7EN ( avertissement : spoilers et langage grossier et violence très classé R), car même après avoir déballé le présent, vous vous trouvez dans la situation suivante: vous avez maintenant nil, ou une boîte contenant quelque chose (mais vous ne savez pas quoi).

Vous connaissez peut-être le type de quelque chose . Par exemple, si vous déclarez la variable comme étant de type, [Int:Any?]alors vous sauriez que vous avez un dictionnaire (potentiellement vide) avec des indices entiers qui donnent un contenu encapsulé de n'importe quel type ancien.

C'est pourquoi le traitement des types de collection (dictionnaires et tableaux) dans Swift peut devenir un peu poilu.

Exemple concret:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)
Virgule
la source
nice 😊 😊😊 😊 😊😊 😊 😊😊
SamSol
j'adore l'analogie! Alors, y a-t-il une meilleure façon de déballer les boîtes? dans votre exemple de code
David
Le chat de Schrödinger est dans la boîte
Jacksonkr
Merci de me confondre ... Quel genre de programmeur offre un cadeau d'anniversaire qui est vide de toute façon? kinda mean
delta2flat
@Jacksonkr Ou pas.
amsmath
13

Swift accorde une grande importance à la sécurité du type. L'ensemble du langage Swift a été conçu dans un souci de sécurité. C'est l'une des caractéristiques de Swift et que vous devriez accueillir à bras ouverts. Il aidera au développement d'un code propre et lisible et aidera à empêcher votre application de planter.

Toutes les options de Swift sont délimitées par le ?symbole. En définissant le ?après le nom du type dans lequel vous déclarez comme facultatif, vous ne transtypez pas essentiellement le type dans lequel se trouve avant le ?, mais plutôt comme le type facultatif .


Remarque: Une variable ou un type Intn'est pas le même que Int?. Ce sont deux types différents qui ne peuvent pas être utilisés l'un sur l'autre.


Utilisation facultative

var myString: String?

myString = "foobar"

Cela ne signifie pas que vous travaillez avec un type de String. Cela signifie que vous travaillez avec un type de String?(String Optional ou Optional String). En fait, chaque fois que vous essayez de

print(myString)

au moment de l'exécution, la console de débogage imprimera Optional("foobar"). La Optional()partie " " indique que cette variable peut ou non avoir une valeur au moment de l'exécution, mais se trouve justement qu'elle contient actuellement la chaîne "foobar". Cette Optional()indication " " restera à moins que vous ne fassiez ce qu'on appelle "déballer" la valeur facultative.

Déballer une option signifie que vous convertissez maintenant ce type comme non facultatif. Cela générera un nouveau type et attribuera la valeur qui résidait dans ce type facultatif au nouveau type non facultatif. De cette façon, vous pouvez effectuer des opérations sur cette variable car elle a été garantie par le compilateur pour avoir une valeur solide.


Conditionally Unwrapping vérifiera si la valeur de l'option est nilou non. Si ce n'est pas le cas nil, il y aura une variable constante nouvellement créée à laquelle la valeur sera attribuée et déroulée dans la constante non facultative. Et à partir de là, vous pouvez utiliser en toute sécurité le non-facultatif dans le ifbloc.

Remarque: Vous pouvez donner à votre constante déballée conditionnellement le même nom que la variable facultative que vous déballez.

if let myString = myString {
    print(myString)
    // will print "foobar"
}

Le déballage conditionnel des options est le moyen le plus propre d'accéder à la valeur d'un optionnel car s'il contient une valeur nulle, tout ce qui se trouve dans le bloc if let ne s'exécutera pas. Bien sûr, comme toute instruction if, vous pouvez inclure un bloc else

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

Le déballage forcé est effectué en utilisant ce que l'on appelle l' !opérateur ("bang"). Ceci est moins sûr mais permet toujours à votre code de se compiler. Cependant, chaque fois que vous utilisez l'opérateur bang, vous devez être sûr à 1000% que votre variable contient en fait une valeur solide avant de forcer le déballage.

var myString: String?

myString = "foobar"

print(myString!)

Ce ci-dessus est un code Swift entièrement valide. Il imprime la valeur de myStringqui a été définie comme "foobar". L'utilisateur verrait foobarimprimé dans la console et c'est à peu près tout. Mais supposons que la valeur n'a jamais été définie:

var myString: String?

print(myString!) 

Maintenant, nous avons une situation différente entre nos mains. Contrairement à Objective-C, chaque fois qu'une tentative est faite pour déballer de force un optionnel, et que le optionnel n'a pas été défini et l'est nil, lorsque vous essayez de déplier le optionnel pour voir ce qu'il y a à l'intérieur de votre application se bloque.


Déballage avec moulage de type . Comme nous l'avons dit précédemment, bien que vous soyez unwrappingle type facultatif, vous convertissez en un type non facultatif, vous pouvez également convertir le type non facultatif en un type différent. Par exemple:

var something: Any?

Quelque part dans notre code, la variable somethingsera définie avec une valeur. Peut-être utilisons-nous des génériques ou peut-être y a-t-il une autre logique qui changera la situation. Donc, plus tard dans notre code, nous voulons l'utiliser somethingmais toujours pouvoir le traiter différemment s'il s'agit d'un type différent. Dans ce cas, vous voudrez utiliser le asmot - clé pour déterminer ceci:

Remarque: L' asopérateur est la façon dont vous tapez cast dans Swift.

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

Notez la différence entre les deux asmots-clés. Comme avant, lorsque nous avons déballé de force un optionnel, nous avons utilisé l' !opérateur bang pour le faire. Ici, vous ferez la même chose, mais au lieu de diffuser comme un simple non-facultatif, vous le diffusez également en tant que Int. Et il doit pouvoir être abattu car Int, sinon, comme utiliser l'opérateur bang lorsque la valeur est, nilvotre application plantera.

Et pour pouvoir utiliser ces variables dans une sorte ou une opération mathématique, elles doivent être déballées pour ce faire.

Par exemple, dans Swift, seuls les types de données numériques valides du même type peuvent être exploités les uns sur les autres. Lorsque vous transtypez un type avec le, as!vous forcez le downcast de cette variable comme si vous étiez certain qu'elle est de ce type, donc sans danger pour fonctionner et ne pas planter votre application. Ce n'est pas grave tant que la variable est effectivement du type vers lequel vous la lancez, sinon vous aurez un désordre entre les mains.

Néanmoins, le cast avec as!permettra à votre code de se compiler. En castant avec un as?c'est une autre histoire. En fait, as?déclare votre Intcomme un type de données complètement différent.

C'est maintenant Optional(0)

Et si vous avez déjà essayé de faire vos devoirs en écrivant quelque chose comme

1 + Optional(1) = 2

Votre professeur de mathématiques vous aurait probablement donné un «F». Même chose avec Swift. Sauf que Swift préfère ne pas compiler du tout plutôt que de vous donner une note. Parce qu'en fin de compte, le optionnel peut en fait être nul .

La sécurité d'abord pour les enfants.

Brandon A
la source
Belle explication des types optionnels. A aidé à clarifier les choses pour moi.
Tobe_Sta
0

'?' signifie une expression de chaînage facultative

le '!' signifie une valeur de force
entrez la description de l'image ici

gkgy
la source