Les Optionnelles

J'ai beaucoup de mal a en comprendre le fonctionnement et l'utilité...


 


Donc un discussion sur ce sujet sera peut-être utile a beaucoup ?


Mots clés:

Réponses

  • CéroceCéroce Membre, Modérateur
    juin 2014 modifié #2
    En gros, certaines méthodes en Objective-C te renvoient nil ou NSNotFound quand elles ne parviennent pas à  trouver un objet ou un index qui correspond à  ta demande. Les optionnelles de Swift permettent de formaliser cela. En mettant un ? au type, on indique que la méthode n'est pas sûre de pouvoir répondre à  la demande.

    Ce qui me semble un peu plus compliqué, c'est après: soit on a déterminé qu'une valeur a bien été renvoyée, auquel cas on peut écrire variable! pour forcer son utilisation, soit on se trimbale cette incertitude dans la suite du code.

    Je dirais, relis la doc. La notion me semble un peu compliquée, mais c'est bien expliqué.
  • BenjoBenjo Membre
    juin 2014 modifié #3

    Il est dit dans la doc :



    You can use if and let together to work with values that might be missing. These values are represented as optionals. An optional value either contains a value or contains nil to indicate that the value is missing. Write a question mark (?) after the type of a value to mark the value as optional.



     


    Et on a comme exemple :



    var optionalString: String? = "Hello"
    optionalString == nil

    var optionalName: String? = "John Appleseed"
    var greeting = "Hello!"
    if let name = optionalName {
    greeting = "Hello, \(name)"
    }

    En gros c'est ce qu'a dit Céroce. Une variable optionnelle n'est pas "certaine" d'avoir une valeur. Et si avec ce code tu changes optionnaleName en nil :



    var optionalString: String? = "Hello"
    optionalString == nil

    var optionalName: String? = nil
    var greeting = "Hello!"
    if let name = optionalName {
    greeting = "Hello, \(name)"
    }
    else {
    greeting = "Hello John Doe !"
    }

    Et bien tu n'as aucune erreur qui apparait et greeting aura comme valeur "Hello John Doe".


  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #4
    Autre exemple simple, une fonction qui te retourne un Int indiquant l'index d'une chaà®ne dans un tableau... si la chaà®ne existe et est en 1ère position ça te retourne 0, si elle est en 2ème position ça te retourne 1, ... mais si elle est pas trouvée ??


    La seule façon en ObjC c'est de retourner un nombre un peu spécial tellement grand qu'il est improbable. Il s'agit de NSNotFound, qui est une constante ayant pour valeur le plus grant Int possible. ça fait un peu bidouille quand même tu ne trouves pas ?


    En ObjC pour les "objets qui n'existent pas" on a le mot clé `nilË‹... mais quand ce ne sont pas des objets qu'on manipule mais des Int, des Double... on n'a rien d'équivalent.


    En Swift, on peut utiliser `nil` pour tous les types, que ce soit des Objets (instances de classes) ou des struct ou des Int ou Double ou même des enums.

    Par contre la différence c'est qu'en Swift on peut marquer tout type comme étant optional (avec le "?" derrière le type de la variable) et pouvant être nil, mais aussi seuls les types marqués optional peuvent être nil.

    Ainsi une 'var str: String' ne peut pas valoir nil (alors qu'une "NSString *str" peut, elle). Si tu veux indiquer que ta String peut potentiellement valoir nil il faut l'indiquer explicitement en lui donnant un type optional.


    Cela permet d'avoir des fonctions par exemple qui prennent en paramètre des String qui sont garanties d'être non-nill, ou des fonctions qui retournent une String qui est garanti d'être non-nil, tant que tu ne définis c'est paramètres qu'avec le type "String" et pas "String?".


    Au moins avec Swift :

    1) On sait explicitement quand une fonction peut retourner nil ou pas, peut prendre des arguments nil ou doit absolument prendre des arguments non-nil

    3) tous les types peuvent être marqués Optional et peuvent ainsi être nil... même les Int et les Struct (et pas que les NSObject comme en ObjC)
  • muqaddarmuqaddar Administrateur


    Ainsi une 'var str: String' ne peut pas valoir nil (alors qu'une "NSString *str" peut, elle). Si tu veux indiquer que ta String peut potentiellement valoir nil il faut l'indiquer explicitement en lui donnant un type optional.


    Cela permet d'avoir des fonctions par exemple qui prennent en paramètre des String qui sont garanties d'être non-nill, ou des fonctions qui retournent une String qui est garanti d'être non-nil, tant que tu ne définis c'est paramètres qu'avec le type "String" et pas "String?".


    Au moins avec Swift :

    1) On sait explicitement quand une fonction peut retourner nil ou pas, peut prendre des arguments nil ou doit absolument prendre des argmulenyd non-nil

    3) tous les types peuvent être marqués Optional et peuvent ainsi être nil... même les Int et les Struct (et pas que les NSObject comme en ObjC)




     


    Oui, les types marqués Optional (?) peuvent être nil, donc là , dans cet exemple de la WWDC (session 402), on voit bien que la variable devient nil suite au retour appellé sur uné clé invalide d'un dico.


     




    let numberOfLegs = ["ant": 6, "snake": 0, "cheetah": 4]
    let possibleLegCount: Int? = numberOfLegs["aardvark"]
    if possibleLegCount == nil {
    println("Aardvark wasn't found") } 

    Par contre, en vérifiant dans le PlayGround, non seulement  le caractère "?" autorise la valeur nil, mais ça la définit par défaut.


     


    Ainsi:


     



    let possibleLegCount: Int?




     


    me renvoie nil.


     


    Si jamais on veut une valeur nil par défaut, pas la peine d'écrire:



    let possibleLegCount: Int? = nil
  • Moi c'est 



    if let name = optionalName

    qui m'a un peu surpris quand j'ai lu cette section.  Je m'attendais à  une comparaison avec nil, comme on ferait dans biens des langages.


  • AliGatorAliGator Membre, Modérateur

    Moi c'est 


    if let name = optionalName
    qui m'a un peu surpris quand j'ai lu cette section.  Je m'attendais à  une comparaison avec nil, comme on ferait dans biens des langages.

    En fait cette syntaxe "if let" est une syntaxe condensée (mais qui répond à  un cas tellement commun que du coup tu la vois un peu partout).
    if let name = optionalName {
    ... code utilisant name ...
    }
    Est équivalent à  :
    if optionalName { // si optionalName n'est pas nil / a une valeur
    let name = optionalName! // on peut "unwrapper" optionalName puisqu'on sait qu'il n'est pas nil
    ... code utilisant name ...
    }
    Donc "if let x = y" à  la fois teste si y est nil, et s'il n'est pas nil l'affecte à  x et exécute la boucle. Comme souvent quand tu testes si un optional est nil ou pas, c'est pour utiliser ensuite sa valeur dans le corps de la boucle, la syntaxe "if let" est assez pratique et fait du deux en un.

    Mais si tu ne veux QUE tester si l'optional est nil ou pas, tu peux juste faire "if optionalName" qui vaudra vrai si optionalName a une valeur et faux s'il est nil.
  • PyrohPyroh Membre

    Autant j'ai tout de suite compris l'intérêt des optionnals et leur puissance autant je ne comprends pas ceux qui sont implicitly unwarped.


     


    J'ai vu dans les ref d'API que certaines fonctions/méthodes renvoyaient du String! (par exemple) ça veut dire quoi ? Qu'on a un optionnel unwarpé d'office ? Pourquoi dans ce ca ?


     


    J'ai un peu du mal si quelqu'un pouvait éclairer ma lanterne ça serait sympa  ;)


  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #9
    Alors oui les "implicitly unwrapped optionals" sont un peu plus complexes à  comprendre.

    En résumé, c'est pour déclarer un optional mais qu'on utiliserait comme s'il ne l'était pas. Comme si on déclarait une "var variable: Type?" mais qu'on utilisait toujours "variable!" au lieu de "variable" dans le reste du code pour forcer l'optional à  être unwrappé (evidemment si l'optional est nil, cela va planter au runtime)



    Leur intérêt est forcément moins clair, mais en gros c'est pratique :

    1. Cas de références croisées et de valeurs nil "que pendant l'init"

    Dans des cas où on a des références croisées entre classes, par exemple une classe Person qui aurait une propriété "creditCard: Card" et une classe "Card" qui aurait une propriété "owner: Person". Si on considère dans notre modèle que une Person a toujours carte de crédit et qu'une carte de crédit a toujours un propriétaire, on a une référence croisée. Dans ce cas :
      • d'une part il faut faire gaffe aux "strong reference cycles"
      • mais en plus, on ne veut pas ni que la propriété creditCard puisse être nil ni que owner puisse être nil une fois que les 2 objets sont créés. Par contre comme on ne peut pas créer les 2 objets en parallèle / exactement en même temps, alors juste le temps de l'initialisation au tout tout début quand on va créer notre objet Person, sa propriété creditCard va forcément être nil... juste le temps qu'on crée, à  la ligne suivante sans doute, l'objet Card() à  mettre dedans.
      • Donc d'un côté on est obligé d'avoir un optional car juste le temps de la création ça va forcément être nil le temps qu'on crée l'autre objet à  mettre dedans, mais d'un autre côté une fois que les 2 objets sont créés on sait que l'un n'existera jamais sans l'autre et ne pourra plus jamais redevenir nil, et donc on veut pouvoir utiliser la propriété creditCard d'une Person sans se poser de questions, comme si c'était une propriété standard et pas Optional.

        Dans ce cas d'usage (expliqué en détail dans la vidéo... 403 il me semble, ou peut-être 404, où ils causent des strong reference cycles et de comment les éviter), un "implicitly unwrapped optional" est pratique car il n'y a que pendant la courte phase d'initialisation qu'on a besoin que ce soit un optional, mais ensuite on veut l'utiliser comme un type non-optional.

        ---

        2. Lors du Bridging ObjC

        Dans le cas du bridging de code Objective-C en Swift, les NSString sont traduites en objets String!. Les NSString sont potentiellement à  nil, donc ça ne peut pas être juste des "String" non optionnelles, mais forcément au minimum des "String?". Mais en même temps, pour pouvoir utiliser la syntaxe exactement comme on utiliserait la même syntaxe en ObjC (où on appelle les méthodes directement sur l'objet sans avoir à  l'unwrapper à  chaque appel), ces APIs retournent plutôt des "String!", donc des String qui sont optional, mais qu'on utilise comme si ça n'en était pas.
        Bien sûr, si on les utilise alors qu'elles sont nil, ça va planter, alors qu'en ObjC ça ignorait silencieusement l'appel (un appel de méthode sur nil ne fait rien en ObjC). Mais le fait que "String!" soit implicitly unwrapped n'empêche pas de pouvoir tester si l'optional a une valeur ("if implOptString { ... }")


        Je te le concède, c'est un concept un peu plus difficile à  appréhender. C'est clairement beaucoup moins fréquent d'avoir à  déclarer des implicitly-unwrapped-optional dans notre code que des Optionals classiques. Mais pour tout ce qui est code bridgé, c'est sûr que tu vas en rencontrer pas mal. Faut voir ça comme "un type est est optional, mais qu'on utilise à  l'appel comme s'il ne l'était pas" ou plutôt "qu'on utilise lors des appels comme si on unwrappait systématiquement sa valeur".

        Mettre un "!" lors de la déclaration ça évite de devoir mettre des "!" ensuite un peu partout dans tous les appels où l'on utilise la variable/propriété. C'est discutable (ça enlève un peu de safety) mais c'est pratique surtout pour le bridging ou le cas 1 évoqué dans la vidéo.
  • Merci pour la réponse, c'est encore plus complexe que ce que j'imaginais.


    Je vais revoir la vidéo pour essayer de vraiment saisir le truc.  :)


Connectez-vous ou Inscrivez-vous pour répondre.