[Swift] Intérêt de "if let"

Bonsoir,


 


Je commence à  regarder Swift de près et l'utilisation de "if let" m'interpelle.


 


On voit beaucoup de code du type :



if let detail = self.detailItem {
if let imageView = self.detailImageView {
imageView.image = UIImage(named: detail)
}
}

Y-a-t-il un intérêt à  écrire cela plutôt que :



if self.detailItem != nil {
if self.detailImageView != nil {
self.detailImageView.image = UIImage(named: self.detailItem!)
}
}

L'utilisation et l'affectation de 2 variables temporaires dans le 1er code me gêne un peu.


A moins qu'il y ait un intérêt particulier autre que celui de rendre la lecture plus difficile.


 


Réponses

  • colas_colas_ Membre
    septembre 2015 modifié #2

    Sans répondre à  ta question, tu peux déjà  rendre le code plus concis:



    if let detail = self.detailItem,
    imageView = self.detailImageView {
    imageView.image = UIImage(named: detail)
    }
    }

  • AliGatorAliGator Membre, Modérateur
    septembre 2015 modifié #3
    Je te renvoie à  mon article "Thinking un Swift, Part 1: Saving Ponies" sur mon Blog, qui va répondre à  ta question sur if let.
  • zoczoc Membre
    septembre 2015 modifié #4

    Personnellement je trouve la première écriture plus lisible... Le but du if let c'est quand même d'unwrapper les optionnals et éviter de se retrouver avec des ! de partout dans le corps du if...


     


    Après, la version de colas_ est encore meilleure.

  • AliGatorAliGator Membre, Modérateur
    En gros le "if let" permet :
    • D'avoir une syntaxe plus concise
    • De faire d'un seul coup à  la fois le test de nullité ET l'unwrapping en une seule instruction
    • D'utiliser une instruction sans doute optimisée par le compilateur, du fait justement que ça fait les 2 opérations d'un coup plutôt que de faire le test de nil puis plus tard d'unwrapper (optimisation de l'utilisation des registres en regroupant la manipulation de la même variable / même registre au même moment)
    • De détecter immédiatement un "Code Smell". Clairement dès que je vois un force-unwrapping "!" dans du code, cela me met tout de suite la puce à  l'oreille (ça sent du code qui tue des poneys, ça) et je me dit que c'est un code qui risque de planter. Même si en pratique finalement tu as protégé l'exécution de ton code qui utilise "!" par un "if" qui fait le test précédemment :
      • il faut lire le flow du code pour être sûr que ce "!" tu as bien le droit de le faire et que y'a pas un risque de plantage au runtime, c'est un peu risqué
      • si tu changes le code plus tard par exemple tu décides de changer la condition et teste autre chose que la nullité de ta variable, mais que tu oublies de protéger ton "!", ou si finalement tu décides d'unwrapper une autre variable mais oublie de changer la condition, ou si tu fais une faute de frappe en tapant ton code et que tu tapes "self.detailItems" dans ta condition (en imaginant que c'est aussi une propriété qui existe, de type "Array?" par exemple) alors que tu utilises "self.detailItem" dans le reste du code, tu vas avoir un crash. Alors qu'en mettant au même endroit avec un "if let" le test et l'unwrap, ce risque n'existe plus, puisque si tu changes tu n'as qu'à  changer qu'à  un seul endroit
    Moi dès que je vois un force-unwrap "!" dans le code de toute façon ça laisse préssuposer que y'a un truc qui cloche, en tout cas un truc dangereux. Un peu comme quand je vois une faute d'orthographe, ça me fait tout de suite mal rien qu'à  la voir et ça me gêne automatiquement pendant la lecture, là  c'est pareil. Peut-être que tu as mis une condition "if" qui va bien plus haut pour protéger cet accès, mais demain le code à  l'intérieur de ton "if" va peut-être grossir, tu vas peut-être rajouter des choses au fur et à  mesure que ton code évolue, et ton "if maVariableOptionelle" va de plus en plus s'éloigner de ton utilisation de "maVariableOptionelle!" et ça va devenir de moins en moins évident que tu as vraiment le droit de force-unwrapper cette variable sans risque.


    Bon, et puis sinon, pour ton exemple, tu pourrais aussi utiliser l'optional chaining pour ta detailImageView, si tu n'aimes pas les "if let" ;-)
  • Merci pour ces explications et c'est vrai que j'ai mis le ! après "self.detailItem" parce que Xcode me l'a demandé.


     


    Ca a été mon premier poney... Désolé AliGator !


     


    Ce qui me gène est l'utilisation des 2 variables temporaires.


    A une époque, il fallait plutôt les éviter sauf s'il y avait plein d'appel par exemple à  une variable d'un tableau.


    Maintenant cela a probablement beaucoup moins d'importance.


    Du coup l'avantage de faire le test de nullité et l'unwrapping en une seule instruction est sans doute prépondérant.


    Même (ou surtout) si l'unwrapping est encore obscur pour moi.




  • A une époque, il fallait plutôt les éviter sauf s'il y avait plein d'appel par exemple à  une variable d'un tableau.




     


     


    J'ai toujours pensé (sans vérifier) que créer une variable temporaire pour factoriser un appel à  une autre variable n'avait pas d'impact en terme de mémoire ou de vitesse. Je me dis que le compilateur l'aurait fait à  ma place.

  • De toute manière le compilateur est plus malin que nous pour les micros-optimisations. Alors autant privilégier la lisibilité du code à  des spéculations sur la manière dont vas fonctionner la boà®te noire. Moi j'aime bien le if let.

  • AliGatorAliGator Membre, Modérateur

    Ce qui me gène est l'utilisation des 2 variables temporaires.
    A une époque, il fallait plutôt les éviter sauf s'il y avait plein d'appel par exemple à  une variable d'un tableau.
    Maintenant cela a probablement beaucoup moins d'importance.
    Du coup l'avantage de faire le test de nullité et l'unwrapping en une seule instruction est sans doute prépondérant.

    Cela te gêne pour la lisibilité de ton code, ou pour l'optimisation de ce dernier ?
    Genre en lisant ça tu te dis intérieurement "ah mais là  il va prévoir de la place pour une variable supplémentaire pour pas grand chose, c'est du gaspillage" ?

    Parce que rassures-toi, bien au contraire :

    - Le compilateur est bien bien plus intelligent que ça et avec les optimisations de LLVM qu'on a de nos jours c'est déjà  traité tout seul

    - Et en plus un "if let" est certainement bien plus optimisé. Moi ce que j'imagine c'est que :
    1) avec la solution "!= nil" puis "force-unwrap!" ensuite, il a besoin d'utiliser un registre pour mettre ton optional dedans et le comparer à  nil au début, puis il va exécuter quelques autres lignes de code, et il aura besoin de remettre ton optional dans le registre à  nouveau si jamais le registre a été utilisé pour autre chose entre temps, pour cette fois le unwrapper, et mettre le résultat unwrappé (le résultat du "maVar!") dans un registre (une variable temporaire créée implicitement si tu préfères) pour que le reste de ton code puisse l'utiliser.
    2) Alors qu'avec un "if-let", il met certainement ton optional dans un registre, et ensuite pendant qu'il est là  en profite pour faire le test de nil (JNZ) et le unwrapping et mettre le résultat dans un registre, sans doute le même d'ailleurs (ta variable temporaire).
    3) Bilan avec un if-let il n'y a sans doute qu'un seul registre d'utilisé et surtout ta variable n'est déplacée dans le registre qu'une seule fois, alors que sans elle devra potentiellement être déplacée une fois pour le test à  "!= nil" et une fois pour le unwrapping plus tard.

    Bbon, évidemment, je simplifie exagérément, sous le capot je sais pas comment ça marche en vrai mais c'est certainement plus compliqué, mais bon je pense que tu vois l'idée.

    En bref si ta crainte c'est que l'utilisation d'une variable temporaire ralentit le programme, parce que ça l'oblige à  créer un espace de stockage pour cette variable temporaire ou quoi, détrompe-toi, non seulement sans le if-let il a besoin aussi d'une sorte de variable temporaire pour mémoriser le résultat du "x!" " c'est juste qu'elle est implicite " mais en plus ça même si ce n'était pas le cas ça resterait sans doute bien plus optimisé de faire les opérations sur ton optional toutes concentrées (donc un seul MOV dans le registre pour faire les opérations dessus ensuite) qu'éparpillées.

    Bon c'est conceptuel, y'a tellement d'optimisations faites par le compilateur que c'est pas si simpliste, mais l'idée est là , et ce qu'il faut retenir c'est que pour ce genre de cas il faut laisser le compilateur faire son boulot plutôt que de coder en disant "en évitant d'avoir une variable intermédiaire je vais optimiser des trucs" (ce qui en pratique sera certainement faux).
  • J'ai fait un petit test avec le code du 1er post :


    - compilé en "release", celui avec les if let fait 4 octets de moins,


    - et est légèrement plus rapide


     


    Avec ça et vos explications, le choix est donc le "if let".


     


    Merci pour vos réponses.


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