[RESOLU] Comment faire pour qu'une action ne se déclenche qu'après la fin d'une autre

busterTheobusterTheo Membre
décembre 2017 modifié dans Xcode et Developer Tools #1

Bonjour,


comment faire pour qu'une action ne se déclenche qu'après la fin d'une autre action ?


 


Moi, je fais cela, mais je sais que c'est petit :



montageDent()

DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.creerMasque()
}

creerMasque() ne doit être lancée que quand montageDent() a achevé son boulot.


 


Mon problème est que j'estime le temps en faisant des essais : c'est un peu aléatoire.


 


Je sais qu'il s'agit des "threads", et après m'être mangé des tonnes de tutos, je ne suis toujours pas à  l'aise.


Mots clés:
«1

Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur

    Il y a des appels asynchrones dans montageDent() ?


     


    Si non, pas de problème, il ne faut qu'appeler creerMasque() après montageDent ; tu n'as pas besoin des "threads"


  • Je dirais une notification ?

    Histoire de participer !
  • Joanna CarterJoanna Carter Membre, Modérateur
    décembre 2017 modifié #4


    Je dirais une notification ?

    Histoire de participer !




    Ah. Utiliser une masse pour ouvrir un noix ;)
  • CéroceCéroce Membre, Modérateur
    décembre 2017 modifié #5
    Ta méthode creerMasque() devrait prendre une closure en paramètre:
     
    func creerMasque(completion: @escaping () -> ()) {
    // Appeler completion() quand le traitement se termine.
    }
    Ce n'est nécessaire que si le code de creerMasque() est asynchrone, comme le dit Joanna.


  • Ah. Utiliser une masse pour ouvrir un noix ;)




    Bin quoi, tu n'aimes pas la farine de noix ? Moi j'adore pour préparer des desserts sans gluten. Même chose avec la farine de noisette.



  • Bonjour,


    comment faire pour qu'une action ne se déclenche qu'après la fin d'une autre action ?


     




    Utiliser un bloc de code et une completion ? 

  • Oula, je résume :


     


    Je dirais function pour une méthode ou une fonction (là , on s'en fout).


     


    On lance une function TRUCa.


    Dans TRUCa, il se passe plein de choses qui modifient pleins de choses (variables, positions, etc).


     


    Puis on lance  une function TRUCb.


    Mais cette function travaille sur les variables, positions, etc modifiées par TRUCa.


     


    Autrement dit TRUCb ne doit démarrer seulement quand TRUCa a terminé ses actions.


     


    Voilà  l'histoire, dite autrement.


     


    Sans quoi, celle de châtaigne n'est pas mal non plus.



  •  


     


    Utiliser un bloc de code et une completion ? 

    je pense que c'est une piste, mais je galère avec ce truc


  • Une closure (ou block en Objective-C) semble la solution que tu cherches.


    Tu n'as jamais utilisé URLSession ? C'est un exemple courant de traitement asynchrone.




  •  


    On lance une function TRUCa.


    Dans TRUCa, il se passe plein de choses qui modifient pleins de choses (variables, positions, etc).


     


    Puis on lance  une function TRUCb.


    Mais cette function travaille sur les variables, positions, etc modifiées par TRUCa.


     


    Autrement dit TRUCb ne doit démarrer seulement quand TRUCa a terminé ses actions.


     




     


    Comme l'animal de la savane et l'ours de poche l'ont souligné, il n'y a aucun problème si les traitements de TRUCa sont synchrones. La question est : Y a-t-il quelque chose d'asynchrone dans TRUCa (attente d'une réponse serveur, traitement lourd dans un thread, et..) ?

  • busterTheobusterTheo Membre
    décembre 2017 modifié #12

    Heeeeuuuuuhhhh Draken, ja sais pas "asynchrone" ou pas mais TRUCa fait des choses normales (pas de problème de temps qui s'écoule, mais il s'écoule), puis TRUCb ne doit démarrer, que uniquement et seulement et etc, qu'après que TRUCa est terminé ses choses normales ou pas.


     


    Larme, je crois grave aux completion..., mais...




  • Heeeeuuuuuhhhh Draken, ja sais pas "asynchrone" ou pas mais TRUCa fait des choses normales (pas de problème de temps qui s'écoule, mais il s'écoule), puis TRUCb ne doit démarrer, que uniquement et seulement et etc, qu'après que TRUCa est terminé ses choses normales ou pas.


     




    Si TRUCa ne fait que des choses normales, son fonctionnement est séquentiel. Il ne rend donc la main qu'après avoir terminé tous ses traitements. 

  • CéroceCéroce Membre, Modérateur
    décembre 2017 modifié #14

    Heeeeuuuuuhhhh Draken, ja sais pas "asynchrone" ou pas



    Alors le vrai problème est là . Reprenons un peu les bases.

    Processus


    Sur ton ordinateur, tu as des tas de programmes qui s'exécutent en parallèle. C'est possible parce que chacun a son propre processus. Tu peux voir les processus qui tournent avec l'application Moniteur d'activité ou la commande top.


    Si l'ordinateur ne possède qu'un seul coe“ur, alors les instructions s'exécutent les unes à  la suite des autres, dans un flux ininterrompu. Comment est-il alors possible que les programmes s'exécutent en parallèle ? En fait, ce n'est pas le cas, le multitâche n'est qu'une illusion: le système d'exploitation donne un peu de temps d'exécution (de l'ordre de 30 ms) à  chaque processus à  tour de rôle.


    Si l'ordinateur possède plusieurs coe“urs, alors le principe est similaire, à  la différence que le travail sera réparti sur les coe“urs.


    Les processus possèdent leur propre espace mémoire: les autres processus ne peuvent pas y accéder. C'est ce qui empêche un programme de faire planter toute la machine.


    Thread


    Les threads sont parfois aussi appelés "processus légers". En effet, à  l'instar des processus, il s'agit aussi d'unités d'exécution autonomes, mais qui sont des enfants d'un processus et partagent son espace mémoire.

    En pratique


    Une application iOS est presque toujours constituée d'une seul processus. Comme tu le sais certainement, tout ce qui concerne l'interface utilisateur s'exécute sur le thread principal. Maintenant, imaginons que le programme fait une requête réseau sur ce thread principal: la requête est envoyée au serveur et il faut attendre sa réponse, ce qui peut prendre plusieurs secondes... pendant lesquelles le thread principal est bloqué à  attendre la réponse. En d'autres termes: pendant plusieurs secondes, l'interface utilisateur est bloquée: rien ne bouge à  l'écran, et rien ne réagit.


    La solution est d'utiliser un thread pour la requête réseau: l'attente de la réponse se fait alors en parallèle, et le thread principal peut continuer son exécution.


    Pour revenir à  ton problème


    Si le code de TrucB s'exécute sur le même thread que TrucA, alors le code de TrucA ne peut pas être interrompu par TrucB. Du coup, il est inutile de mettre un timer pour attendre que TrucB a fini: si TrucA reprend la main, c'est forcément que TrucB a fini.


    Si TrucB s'exécute sur un autre thread que TrucA, alors il faut un moyen pour que TrucB signale à  TrucA qu'il a terminé. Il y a plusieurs manières de procéder, mais en général, on va utiliser une closure de complétion, çà d TrucB qui appelle du code de TrucA quand il a terminé.


  • Merci pour ces précisions et ce mini cours très explicatif.


     


    Et donc, je crois que, concernant mon problème, la solution serait de lancer TrucA sur un second thread,


    ainsi, je pourrais faire en sorte que TrucB ne démarre que quand TrucA a terminé ses actions.


     


    Au fait, dans la fin de ton explication, je remplace TrucA par TrcB et inversement...


     


    Donc pour un second thread, je crois que c'est avec (DispatchQueue).


     


    Je vais me faire ce tuto qui a l'air pas mal.


     


    Merci encore.


  • Joanna CarterJoanna Carter Membre, Modérateur

    ???? Tu as dit exprès que tu veux que TrucB ne doit pas commencer avant la fin de TrucA, oui?


     


    Dans ce cas là , tu n'as pas besoin de toucher les threads.


     


    Ou, as-tu changer ce que tu veux faire ?


  • CéroceCéroce Membre, Modérateur

    Je rejoins l'avis de Joanna.


    Une précision que j'ai oublié: tout ce qui concerne l'interface utilisateur (dessin y-compris) doit s'effectuer sur le thread principal.




  • Merci pour ces précisions et ce mini cours très explicatif.


     


    Et donc, je crois que, concernant mon problème, la solution serait de lancer TrucA sur un second thread,


    ainsi, je pourrais faire en sorte que TrucB ne démarre que quand TrucA a terminé ses actions.


     


     




     


    M'enfin c'est fou ça !  ???


    On ne cesse de t'expliquer que tu n'as pas besoin de thread, puisque TrucA ne contient que des traitements normaux (selon tes propres explications).



    func mesTrucs() {
      // TrucA rend la main quand il a terminé ses traitements 
      TrucA()
      // TrucB s'exécute quand TrucA a terminé
      TrucB()
    }

    Qu'est-ce tu ne comprend pas dans le mot normal ?


     


    Tu as déjà  tapé des dizaines de milliers de lignes de code, fonctionnant de manière normale, c'est-à -dire en exécution séquentielle. J'exécute A, puis B, puis C, etc ..


     


    a



    A()
    B()
    C()

    Si TrucA contenait un processus asynchrone (réponse à  un Timer, attente d'un événement utilisateur, thread, animation UIView.animate(), etc..), la situation serait différente, mais tu n'arrêtes pas de dire que non, non .. 

  • Puisque tu as l'air totalement perdu et dépassé par le concept d'asynchronisme, on va faire plus simple :


    As-tu dans le code de trucA() une closure quelque part ? Car c'est le cas le plus basique et courant de méthode asynchrone.


    Closure soit inutilisée, soit mise à  nil.


     


    Donne-nous toutes les closures en donnant si possible la class de l'objet les appelants.


     


     


    Exemple concret (que tu peux tester dans playground), cela t'aidera peut-être à  comprendre



    import UIKit
    import Foundation

    PlaygroundPage.current.needsIndefiniteExecution = true
    let urlStr = "http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"
    let session:URLSession = URLSession.shared
    let url = URL.init(string: urlStr)
    session.dataTask(with: url!) { (dataReturned, urlResponse, error) in
        print("Inside the closure")
    }.resume()
    print("After")

    Sortie console :



    $​>After
    $>Inside the closure

    Alors qu'en " théorie ", en tant que débutant tu t'attendrais à  ce que l'ordre de la sortie (les prints) soit différent, parce que tu t'attends à  un ordre séquentiel, les un après les autres dans l'ordre d'écriture/appel.

  • busterTheobusterTheo Membre
    décembre 2017 modifié #20

     


     


    Tu as dit exprès que tu veux que TrucB ne doit pas commencer avant la fin de TrucA, oui?

    Toujours oui.



     


     


     puisque TrucA ne contient que des traitements normaux

    Oui, enfin pour moi...


    Voilà  par ex...



     


    À chaque régénération du path :


    - Récup des points et controls d'après un tableau


    - Y'a de la boucle


    - Y'a du test


    - Y'a du apply(CGAffineTransform


    - Et Pas de closures



    Pour l'ex avec la réponse serveur, aucun pb, et justement là  est ma question : Dans ce cas là , comment tu ferais que le "after" se print justement après : moi j'ai mis un timer..


  • Mettre un timer, cela revient à  ce CommitStrip.


    Qui te dit que cela sera terminé au bout de n secondes?


    La closure (qui en théorie est souvent appelée completionHandler et donc indique qu'elle ne sera appelée que lorsque cela sera terminé) est là  pour ça.


    Je n'aurais pas printé After là , j'aurais printé After à  la place de print("Inside the closure") et appelé une autre méthode au besoin ou juste écrire d'autre lignes de code à  ce moment là .

     


  • C'est exactement ce que je dis au tout début du post.


     


    J'aimerai bien faire du  completionHandler mais je galère un peu : je réessaie.


     


    Merci


  • Pour que ce completionHandler ait du sens, il faut vraiment que tu trouves là  où il y a un traitement asynchrone, sinon il n'a AUCUN intérêt.


    Ou alors tu bloques le thread sans t'en rendre compte.


    Cela reviendrait à  mettre le completion handler sur le print("After"), qui ne sera pas appelé after de toute manière mais bien trop tôt.


  • ouais, c'est ça, et je galère car je dois attendre la fin du tracé d'un path qui se re-fabrique à  partir d'un tableau qui contient les points et controls du path.


  • LarmeLarme Membre
    décembre 2017 modifié #25

    Tu as quelque chose qui prend du temps et de manière asynchrone apparemment. Mais si tu ne files pas de code, ça va être dur de savoir, si tu n'es pas capable de le trouver tout seul.


    Note que certaines méthodes proposent des completionHandler, d'autres non et ils sont parfois optionnels.


    Cf animate() de UIView.



    func animate(withDuration: TimeInterval, animations: () -> Void)
    func animate(withDuration: TimeInterval, animations: () -> Void, completion: ((Bool) -> Void)? = nil)

    J'ai regardé apply(_ :) de UIBezierPath, c'est indiqué que c'est fait directement.


     


    Tu n'as aucun dispatch dans ton code?


  • J'hésite à  mettre du code pour pas trop vous saouler avec du code qui peut être lourd.



     


     


    Note que certaines méthodes proposent des completionHandler, d'autres non et ils sont parfois optionnels

    Ouais c'est bien dommage.


     


    Je viens de me replonger dans mes bouquins et sur le net


    Je vois que l'on peut faire pareil avec les fameuses closures comme sur ce site


    Comme ça 



    firstVC.present(nextVC, animated: true) { print("Done") } 

    J'essaie de faire cela :



    self.montageDent() { self.creerMasque() }

    et évidemment erreur "Argument passed to call that takes no arguments"


  • Avant d'implémenter quoi que ce soit (l'erreur est sûrement dû au fait que tu n'as pas déclaré montageDent() avec une closure), il faut que tu trouves ce qui est asynchrone dans montageDent().



    func methodA(param1:param1, completionHandler: @escaping (Bool?) -> Void) {
        let urlStr = "http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8"
        let session:URLSession = URLSession.shared
        let url = URL.init(string: urlStr)
        session.dataTask(with: url!) { (dataReturned, urlResponse, error) in
            print("Inside the closure")
            //POSSIBILITE B
        }.resume()
        print("After")
       //POSSIBILITE A
    }

    Ecrire completionHandler(true) à  la place de //POSSIBILITE A ne sert à  rien du tout, il devrait être écrit à  la place de //POSSIBILITE B  quand tu as réellement reçu la réponse de la requête web.


  • CéroceCéroce Membre, Modérateur
    Le prototype de la fonction que tu prends en exemple est:
    present(UIViewController, animated: Bool, completion: (() -> Void)? = nil)
    En Swift, si le dernier argument d'une fonction est une closure:
    1) on n'est pas obligé de donner son nom
    2) on peut fermer les parenthèses et mettre le corps de la closure après.

    Ce qu'il faut est que la fonction montageDent() prenne une closure en paramètre, comme je l'ai déjà  montré en page 1:


    func montageDent(completion: @escaping () -> ())
  • Bon alors, j'arrive à  pas planter mais ma 2e action ne se déclenche pas - Je fais ça :


    Ma première action c'est (montageDent) et la seconde est (creerMasque)



    self.montageDent { () -> () in
    self.creerMasque()
    }

    Avec ça :



    func montageDent(completionHandler: @escaping () -> ()) {
    ...

    J'ai fait plein d'essais avec des 



    completion: (oneSuccess: Bool) -> Void)


    completionHandler(true)


    montageDent { (oneSuccess) -> Void in
    if oneSuccess {
    creerMasque()
    }
    }


    Et plein d'autres trucs mais rien de rien


  • Tu dois appeler completionHandler() à  la fin du code de func montageDent(completionHandler: @escaping () -> ()) {} En bref, avant la dernière }.


    Mais je reste d'avis que si ton code est async, tu dois placer judicieusement completionHandler() or cela n'a pas l'air d'être ton cas.


  • Ok merci.


     


    J'ai mis completionHandler() à  la fin de la méthode montageDent, et c'est pire, il manque la moitié des actions de montageDent


     



     


     


    Mais je reste d'avis que si ton code est async, tu dois placer judicieusement completionHandler() or cela n'a pas l'air d'être ton cas

    Tu as certainement raison.


     


    C'est un exemple, mais cela se produit à  plusieurs endroits de ce petit programme, car justement le path doit régulièrement se re-dessiner vu qu'on le modifie régulièrement. C'est le but de ce petit programme qui sera intégré dans un plus gros.


     


    Mais tout le problème vient de ce (setNeedsDisplay())qui prend du temps


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