Bouton prioritaire

tabliertablier Membre
novembre 2012 modifié dans API AppKit #1
Dans Repertoire, J'ai une méthode récursive qui analyse une hiérarchie de dossier-fichier. Classique!

Suite au conseil de Lugdanum, j'ai fait un certain nombre de modifications dont l'ajout d'un bouton "Stop" qui devrait être prioritaire.

Ce que je prévoyais: un clic sur le bouton Stop met un Flag à  YES et la méthode récursive tient compte du flag pour ressortir immédiatement.

Mais, quoi que je fasse, le bouton stop n'est pas vu pendant que cette méthode est exécutée!

Je crois comprendre que la méthode qui se rappelle elle-même ne laisse pas de temps libre pour l'interface utilisateur.

En cherchant à  droite et à  gauche, il me semble que l'on résout cela en utilisant NSThread.

Est-ce que je continue sur cette voie ou dois-je regarder ailleurs?

Réponses

  • AliGatorAliGator Membre, Modérateur
    NSThread est une solution possible, mais pas la plus simple.



    Il y a plein de possibilités de faire du code concurrentiel (qui s'exécute en parallèle). utiliser les threads directement en est une, mais plutôt bas niveau (tu auras beaucoup de choses à  gérer toi-même pour faire du thread-safety, gérer la synchro des ressources, éviter les deadlocks, et anticiper les cas un peu tricky genre race-conditions...), et cela apporte des problématiques en général vite complexes.



    Il y a plein de solutions alternatives plutôt que d'utiliser NSThread directement qui sont encore plus haut niveau ou plus adaptées et t'évitent de te prendre le chou avec divers détails.

    - Par exemple manipuler NSRunLoop pour déclencher une itération de ta reccursivité à  chaque itération de RunLoop, laissant la RunLoop continuer de faire, en plus de ton itération de parcours, les vérifications d'interaction utilisateur.

    - Ou utiliser NSOperationQueue, et empiler tes opérations (à  chaque dossier rencontré, tu crées une nouvelle NSOperation dédiée qui va scanner ce dossier, et tu "empiles" cette NSOperation dans ta NSOperationQueue dédiée pour qu'elle s'exécute quand elle aura du temps dispo)

    - Ou utiliser GCD et dispatch_async pour envoyer des blocs de code s'exécuter de façon asynchrone (donc non bloquante pour l'interface) sur des "Queue" GCD.



    Toutes ces solutions dans leur ensemble te poseront moins de problèmes que NSThread. Celle utilisant NSOperationQueue étant peut-être la plus simple à  comprendre (concept d'empiler des opérations) tout en ayant une API Cocoa.

    Tu crées une sous-classe ScanOperation de NSOperation spécialisée dans ton opération de scan d'un dossier donné en paramètre, et qui boucle sur son contenu, exécute une action donnée pour les fichiers (action que tu peux même envisager passer en paramètre via un block, tiens, tant qu'on y est), et crées d'autres objets ScanOperation quand il rencontre un sous-dossier. Tu vas empiler tes ScanOperations sur une NSOperationQueue et elle s'exécuteront en parallèle (bien plus puissant même qu'un algo reccursif car cela fait les scans des dossiers également en parallèle et pas en séquentiel) et pendant que la NSOperationQueue va les exécuter en arrière-plan (gérant elle-même toutes les subtilités des threads ou queue qu'elle pourra créer pour faire ça sur un thread autre que le main thread) ton main thread et ta main RunLoop pourront toujours intercepter les actions utilisateurs.



    Je t'invite fortement à  lire le Concurrency Programming Guide qui explique pourquoi il faut mieux éviter NSThread, et quelles sont les différentes autres techniques avec leurs avantages et inconvénients, etc.
  • tabliertablier Membre
    novembre 2012 modifié #3
    NSOperationQueue parait bien adapté à  mon problème. Il y a plein de truc que je n'ai pas encore trouvé comme la remonté du comptage dans le niveau du dessus...., mais ça va venir.

    J'ai une question subsidiaire. Dans l'exemple NSOperationSample j'ai trouvé:
    @implementation LoadOperation



    // NSNotification name to tell the Window controller an image file as found

    NSString *LoadImageDidFinish = @LoadImageDidFinish;
    quelle est la porté de LoadImageDidFinish? Globale tant que l'objet LoadOperation existe?

    Ce qui me parait curieux: cette variable déclarée dans l'implementation de LoadOperation est déclarée extern dans le LoadOperation.h. Je ne comprends pas vraiment cela.
  • Oui, c'est une variable globale, comme tous les noms des notifications dans Cocoa.
  • AliGatorAliGator Membre, Modérateur
    novembre 2012 modifié #5
    En fait oui c'est une variable globale, mais normalement pour bien faire ça devrait être déclaré comme une constante et non comme une variable, car les variables globales c'est le mal comme chacun sait (pas thread-safe, tout ça tout ça).

    Et puis surtout il n'y a pas de raison de permettre de changer la valeur vers laquelle pointe LoadImageDidFinish, donc c'est bien plus logique d'en faire une constante.



    Autant les variables globales c'est le mal, autant les constantes sont souvent déclarées dans l'espace global, pour être accessibles de partout (mais en lecture uniquement donc il n'y a pas les soucis qu'on peut avoir avec les variables globales). Seule truc du coup quand tu déclares une constante dans l'espace global, c'est de lui donner un nom assez différenciant pour être à  la fois explicite et éviter que le nom rentre en conflit avec une autre constante.



    Je ne sais pas pourquoi dans l'exemple que tu cites ils n'ont pas déclaré ça en "const" comme pour toutes les notifications qu'on trouve dans Cocoa... ceci dit les exemples d'Apple sont connus pour être juste là  "pour comprendre le principe de base de fonctionnement" donc allant au plus simple pour ne pas t'embrouiller avec des détails, et pas pour être "des modèles de conception et d'archi et de bons usages".





    Pour déclarer une NSString constante dans les règles de l'art :
    • Si c'est une constante interne à  ton .m, qui n'a pas pour but d'être visible de l'extérieur / des autres classes/fichiers, dans ces cas là  tu peux la déclarer que dans le .m, et en tant que "static" (histoire que sa portée soit contrainte au fichier et pas visible ailleurs). N'étant pas publique le nom de la constante importe peu

    // Dans ton .m<br />
    static NSString* const kMaConstante = @&quot;SuperConstante&quot;;<br />
    static int const MaxIterationDepth = 32;<br />
    ...
    
    • Si c'est une constante "publique", comme le nom d'une notification, qui a pour but d'être visible et utilisable par les autres fichiers, alors
      • il faut bien sûr déclarer son existence dans le .h pour que les autres classes connaissent son nom, en utilisant "extern" qui sert à  dire "je te dis juste ici que la constante avec ce nom existe, mais sa valeur est définie autre part"
      • Il faut lui affecter une valeur dans le .m (comme ça elle n'est affectée qu'une seule fois à  un seul endroit dans ton .m même si tu fais 36 imports de ton .h dans ton appli)
      • Et du coup puisqu'elle est publique, tu as intérêt à  lui donner un nom assez explicite et différenciant ; la pratique voulant en général de préfixer cette constante du nom de la classe à  laquelle elle est associée. Comme par exemple la constante UIApplicationDidFinishLaunchingNotification pour la notification envoyée par UIApplication.


    // .h : juste la déclaration, en &quot;extern&quot; pour dire que sa valeur sera donnée ailleurs<br />
    extern NSString* const LoadOperationDidFinishNotification;<br />
    <br />
    // .m : affectation de la valeur (et du coup pas de &quot;static&quot; cette fois car la constante n&#39;est pas confinée uniquement à  être utilisée dans ce .m. De toute façon &quot;extern&quot; et &quot;static&quot; sont orthogonaux<br />
    NSString* const LoadOperationDidFinishNotification = @&quot;LoadOperationDidFinish&quot;;
    




    Voilà  j'espère que ça t'aidera à  y voir plus clair.

    En général donc les notifications dans Cocoa sont déclarées avec "extern" dans le .h, et leur valeur est définie dans le .m (que tu ne vois pas puisqu'il est compilé dans le framework Cocoa)
  • J'ai pratiqué pas mal le C et je connais les notions extern, const et static. Non, ce qui m'a étonné le plus c'est la place de la déclaration: dans le corps de l'implementation. Bien sur, en C il n'y a pas de @implementation. Merci pour cette mise au point.
  • AliGatorAliGator Membre, Modérateur
    Ah ok. Bah si je ne dis pas de bétises, concernant les constantes déclarées ainsi, qu'elles soient dans le @implementation ou pas ne change rien en pratique.



    Eventuellement ça a + de sens uniquement conceptuellement / pour la lecture du code de les mettre à  l'intérieur du @implementation si elles sont "liées" à  ta classe, c'est plus clair à  la lecture, mais au niveau de la compilation si je ne m'abuse ça change rien.
  • Après pas mal de lecture, il y a un truc qui ne me parait pas évident. Dans l'exemple NSOperationSample, le passage de valeur entre opérations se fait en utilisant une notification. Est-ce la seule méthode de passage de valeurs entre opérations. Peut-on utiliser les variables globales par exemple ou les accesseurs pour passer des valeurs? Je peux faire des essais, mais si quelqu'un à  la réponse, ce serait plus simple et rapide.
  • AliGatorAliGator Membre, Modérateur
    'tablier' a écrit:
    Peut-on utiliser les variables globales
    image/crazy.gif' class='bbc_emoticon' alt=' B) ' /> image/whip.gif' class='bbc_emoticon' alt=' >:) ' />
  • Pitié, pas ça!
  • tabliertablier Membre
    novembre 2012 modifié #11
    Je crois que je vais arriver au bout. Mais ça me fait réorganiser un bon tiers du programme!!

    Comme dit dans la doc, c'est quelque chose à  prévoir à  l'origine!
Connectez-vous ou Inscrivez-vous pour répondre.