Partage
  • Partager sur Facebook
  • Partager sur Twitter

Completion Handler et fermetures en Swift

    14 mars 2021 à 21:03:22

    Bonjour,

    Actuellement en train de développer une application iOS j'effectue une requête réseau. La fonction qui charge les réponses associées à la requête possède un paramètre nommé CompletionHandler qui est une fermeture.

    En me renseignant sur le net j'ai compris que c'est ce qui était utilisé lorsque l'on effectuait du code long c'est à dire qui devait attendre une réponse du serveur pour être exécuté.

    Sur le cours Openclassroom il y a un exemple qui correspond à mon cas. Il est expliquer que le retour d'une fonction est renvoyé dès que la fonction est terminé, donc qu'on ne peut pas attendre. Seulement je ne vois pas en quoi c'est un problème, j'imagine la fonction qui charge les réponses de la requête un peu comme ça :

    while BooleenValantVraiSiReponseServeurFautSinon {}
    
    return reponseServeur
    

    Ici, la fonction va rester dans la boucle tant qu'il n'y a pas de réponse du serveur et dès qu'il y en a une alors on la retourne. 

    Du coup je ne vois pas en quoi les fermetures sont utiles car on va, comme dans l'exemple ci-dessus, attendre une réponse du serveur et dès que la réponse arrive on va exécuter le code de la fermeture. Donc le code serait plus comme ça : 

    while BooleenValantVraiSiReponseFauxSinon {}
    
    completionHandler(reponse)

    Et l'appel à completionHandler(reponse) nous retournera la réponse, ce qui marche en effet mais selon moi est juste une complication. Attention je sais très bien qu'elles sont primordiales mais dans l'état actuel de ma vision des choses, je ne comprends pas à quoi les closures servent. 

    De plus je voulais savoir pourquoi les fermetures n'avaient pas accès au contexte de la classe dans laquelle on les déclare. Vu que l'on déclare la fermeture dans le contexte d'une méthode de classe elle-même déclarer dans le contexte de la classe ?

    Merci de votre temps :)



    -
    Edité par Wakate-joker 14 mars 2021 à 21:06:16

    • Partager sur Facebook
    • Partager sur Twitter
      15 mars 2021 à 12:48:41

      Salut, 

      Ta vision des choses est incorrecte. Il n’y a pas de boucle while qui attend la réponse du serveur.

      Si tu as :

      print(1)
      someNetworkCall { print(2) }
      print(3)

      A l’affichage tu auras 1 3 puis 2. Le code n’attend pas le retour de l’appel réseau pour l’exécution. 

      Donc, sans closure, tu ne pourrais jamais récupérer le retour de la requête car ta fonction serait terminée avant d’avoir le retour. 

      Ensuite, ta closure c’est un bloc de code qu´on passe en paramètre. Supposons qu’on ai :

      func someNetworkCall(_ closure: () -> Void) {
          DispatchQueue.main.asyncAfter(.now() + 2) {
              closure()
          }
      }
      
      func x() {
          let a = { print(2) }
          print(1)
          someNetworkCall(a)
          print(3)
      }
      
      

      Donc c’est la même chose que tout à l’heure. J’ai juste fait une implem pour someNetworkCall. a est declaré dans la fonction x. Cependant après l’affichage du 3, cette variable n’existe plus. On affiche 2 car le bloc de code est passé en paramètre de la méthode. C’est bien someNetworkCall qui fait le print(2). Pas x. 

      Concernant le contexte, selon le même principe que juste au dessus, mais j’ai la flemme de donner un exemple le concret car sur mobile c’est vraiment chiant d’écrire du code. Ton instance de classe peut mourir avant que la closure soit exécuté (dans le cas où elle est passée à une autre instance par exemple). tu dois donc passer expliciteent les « trucs » dont tu pourrais avoir besoin dans la closure car potentiellement ils vont mourrir avant d’etre utilise. Il faut donc les copier.

       En espérant avoir été clair. Si c’est pas le cas hésite pas (c’est fondamental comme concept)

       

      • Partager sur Facebook
      • Partager sur Twitter
        15 mars 2021 à 14:32:23

        Tout d'abord merci de ta réponse. 

        Je comprend mieux pourquoi un simple return ne fonctionne pas. Maintenant j'imagine l'exécution de ton code comme ça :

        Lorsque qu'une fonction A (someNetworkCall) est destiné à attendre le résultat d'une requête réseau elle ne se met pas dans la pile comme tout autre appel ? Elle s'exécute sur un thread différent en parallèle de l'exécution principale ? En tout cas c'est comme ça que je le vois.

        Ensuite du coup, passer une closure nous permet d'exécuter un blog de code dans la fonction A pour traiter la réponse quand celle-ci est prête. Or cela est possible uniquement car la closure n'est pas déclaré dans le contexte de la classe mais dans une zone mémoire accessible pour tous les threads, ça expliquerait aussi pourquoi on doit copier les infos (comme l'instance actuelle) en argument (même si j'ai vu que ça se faisait automatiquement avec les captures list).

        Voilà, peux-tu me corriger si je me suis trompé quelque part. J'espère quand même que tout n'est pas faux :/

        J'aimerais aussi revenir sur ce que tu m'as dis "C'est bien le someNetworkCall qui fait le print(2)", est ce que ça veut dire que ce n'est pas la closure qui est appelé mais que l'on remplace simplement la ligne de code closure() dans someNetworkCall par les lignes de code inscrite dans celle-ci ? 

        Et enfin peux-tu me dire à quoi correspond la ligne :

        DispatchQueue.main.asyncAfter(.now() + 2) {
                closure()
            }

        Cela pourrait peut-être m'aider, j'ai bien l'impression que c'est directement relié à mon histoire de thread mais j'aimerais bien ce que la ligne fait concrètement, ça m'intéresse :)

        Merci de ton temps !

        -
        Edité par Wakate-joker 15 mars 2021 à 14:49:17

        • Partager sur Facebook
        • Partager sur Twitter
          15 mars 2021 à 18:46:04

          Effectivement les appels réseaux sont asynchrones et exécuté sur un autre thread que le main pour ne pas bloquer l’ui. Imagine un traitement qui dure 2s, ce serait horrible d’avoir une UI freeze tout ce temps. 

          La closure est accessible comme n’importe quelle variable dans n’importe quel thread. A toi de t’assurer que la variable n’est pas lu dans un thread et modifié dans un autre sous peine de crash. On copie des infos car potentiellement le cycle de vie de la closure est plus long que le cycle de vie de l´instance Qui la crée. Attention au capture list  automatique. C’est une source de leak. Ton instance va retain la closure qui va retain l’instance. Si l’instance meurt alors que la closure existe toujours, elle va leak car elle sera retain par la closure. Et quand la closure devrait être détruite elle sera retain par l’instance. C’est une boucle. Donc pour éviter ces boucles on utilise le mot clé « weak » (closure weak self sur Google tu devrais trouvé)

          Si c’est bien la closure qui est exécuté. Mais elle pas exécuté dans la fonction dans laquelle elle a été crée. Mais dans la fonction qui fait l’appel. Le scope de la fonction qui la crée est terminé. Tout ce qui est dedans a été détruit. 

          la ligne exécuté un bloc de code de maniere asynchrone dans le thread principal après 2s 

          • Partager sur Facebook
          • Partager sur Twitter
            15 mars 2021 à 19:23:07

            Geda a écrit:

            Effectivement les appels réseaux sont asynchrones et exécuté sur un autre thread que le main pour ne pas bloquer l’ui. Imagine un traitement qui dure 2s, ce serait horrible d’avoir une UI freeze tout ce temps. 

            La closure est accessible comme n’importe quelle variable dans n’importe quel thread. A toi de t’assurer que la variable n’est pas lu dans un thread et modifié dans un autre sous peine de crash. On copie des infos car potentiellement le cycle de vie de la closure est plus long que le cycle de vie de l´instance Qui la crée. Attention au capture list  automatique. C’est une source de leak. Ton instance va retain la closure qui va retain l’instance. Si l’instance meurt alors que la closure existe toujours, elle va leak car elle sera retain par la closure. Et quand la closure devrait être détruite elle sera retain par l’instance. C’est une boucle. Donc pour éviter ces boucles on utilise le mot clé « weak » (closure weak self sur Google tu devrais trouvé)

            Si c’est bien la closure qui est exécuté. Mais elle pas exécuté dans la fonction dans laquelle elle a été crée. Mais dans la fonction qui fait l’appel. Le scope de la fonction qui la crée est terminé. Tout ce qui est dedans a été détruit. 

            la ligne exécuté un bloc de code de maniere asynchrone dans le thread principal après 2s 


            D'accord merci tout est clair et c'est très intéressant.

            Si je comprend bien la capture liste va passer l'instance en paramètre de la closure et donc lorsque le garbage collector va vouloir libérer la mémoire associée à l'instance il ne le fera pas car elle est toujours référencée par le paramètre, et de même pour la closure ? 

            Ok je viens de check sur le net est en effet ça a l'air d'être ça, donc si je comprend bien la capture list passe en paramètre une référence strong à la closure ? Pourquoi ne pas passer une référence weak ? Ce doit-être que j'ai mal compris quelque chose ...

            En ce qui concerne les threads, un thread principal est le thread lié à un programme que l'on est en train d'exécuter uniquement c'est ça ? Et il peut-être composé de plusieurs sous-thread ?

            Je ne vois pas pourquoi on doit attendre 2 secondes avant de le lancer ? 

            Enfin bon, mis à part ça tout est clair je te remercie beaucoup :)

            -
            Edité par Wakate-joker 15 mars 2021 à 19:25:55

            • Partager sur Facebook
            • Partager sur Twitter
              15 mars 2021 à 20:54:29

              1) Oui c’est ça. On parle pas vraiment de Garbage collector mais oui c’est ça. 

              2) Oui c’est passé en strong par défaut. Pourquoi ? Je ne sais pas. Peut être parce que certains types ne peuvent pas être passé en weak. Donc pour simplifier... Tu peux toujours la passer en weak en faisant [weak self] in

              3) Le thread principal C’est le thread qui est utilisé pour l’affichage. Pour ce qui est des sous-thread je suis ps sur que ce soit le terme technique. Je prefere eviter de dire une betise la. Mais tu peux avoir plusieurs threads concurrent sur le main.

              4) C’était juste pour illustrer un traitement lourd qui prend du temps. Le print mettra 2s avant de s’afficher. Ca peut être n’importe quel appel réseau ou gros traitement (ça arrive quand même assez peu dans le mobile)

              • Partager sur Facebook
              • Partager sur Twitter
                15 mars 2021 à 22:51:05

                Geda a écrit:

                1) Oui c’est ça. On parle pas vraiment de Garbage collector mais oui c’est ça. 

                2) Oui c’est passé en strong par défaut. Pourquoi ? Je ne sais pas. Peut être parce que certains types ne peuvent pas être passé en weak. Donc pour simplifier... Tu peux toujours la passer en weak en faisant [weak self] in

                3) Le thread principal C’est le thread qui est utilisé pour l’affichage. Pour ce qui est des sous-thread je suis ps sur que ce soit le terme technique. Je prefere eviter de dire une betise la. Mais tu peux avoir plusieurs threads concurrent sur le main.

                4) C’était juste pour illustrer un traitement lourd qui prend du temps. Le print mettra 2s avant de s’afficher. Ca peut être n’importe quel appel réseau ou gros traitement (ça arrive quand même assez peu dans le mobile)

                D'accord merci beaucoup de tes réponses. Je pense que tout est clair pour moi :)

                • Partager sur Facebook
                • Partager sur Twitter

                Completion Handler et fermetures en Swift

                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                • Editeur
                • Markdown