Comment savoir qd une NSView est affichée ?

Paisible.frPaisible.fr Membre
09:01 modifié dans API AppKit #1
J'ai une NSView avec une NSOutlineView. Je peuple l'outlineview lors du AwakeFromNib. Malheureusement ce peuplement est extrèment long à  cause d'une part du nombre d'éléments à  ajouter et des calculs nécessaires à  la création des valeurs du contenu.

Après optimisation j'arrive sur un MacBook autour des 10/15 secondes d'attente au démarage pour peupler mon OutlineView.  Je ne pense pas pouvoir gagner beaucoup plus, si ce n'est quelques microsecondes par-ci par-là . Cela reste encore trop élevé pour être acceptable. Au dessus de 5 secondes l'utilisateur à  le sentiment que l'application est plantée et perd patience.

Mon idée pour remedier à  cela était de retirer ce peuplement du AwakeFromNib et de le reporter plus tard lors du premier affichage de la vue. Ceci afin d'afficher une Progress Bar et un message le temps d'effectuer le peuplement.

Il faudrait que la NSView s'affiche, puis que je lance le peuplement une fois l'affiche de celle-ci terminé.

Problème :
- Comment intercepter le moment où la NSView est affichée ?

Réponses

  • NoNo Membre
    09:01 modifié #2
    Basiquement, il n'y a pas de moyen simple pour intercepter ça...
    Tu peux bidouiller avec drawRect: en surchargeant cette méthode, mais ça devient un poil crade.

    Par contre, 2 cas :
    1: c'est l'utilisateur qui déclenche l'affichage de la NSView (par exemple clic sur un onglet, sur un bouton, etc...) : c'est dans le code de prise en charge ce cette action qui va prendre en charge ton chargement.
    2: la vue est affichée tout de suite après le chargement du nib : awakeFromNib reste le meilleur compromis.

    D'un autre coté, tu peux charger les données à  afficher dans la NSOutlineView dans un NSArray dans un thread. Le temps de ce chargement, NSOutlineView est masquée, et à  sa place tu peux afficher une NSProgressBar.
    Lorsque le thread se termine, avec un p'tit performSelectorOnMainThread, tu peux masquer la progressBar et rendre visible l'outlineView.
    Ainsi, le temps de chargement peut être long, mais l'interface n'est pas figée (donc reste réactive pour l'utilisateur : pas de pointeur balle de plage, les menus restent utilisables, etc...).
    Et l'information du progressBar suffit à  le renseigner sur la progression du chargement.
  • CéroceCéroce Membre, Modérateur
    09:01 modifié #3
    La NSView contient-t-elle l'Outline View ? C'est pour comprendre.

    Pour répondre à  ta question:
    - quand la vue s'affiche, sa méthode -drawRect: est appelée. Tu peux utiliser un simple booléen pour savoir si c'est la première fois.
    - autre solution: utiliser la méthode -viewWillMoveToWindow:

    Cependant, je veux attirer ton attention sur le fait qu'a priori, il faudrait que tu crées un thread séparé pour les calculs; autrement, tu vas bloquer l'interface utilisateur. Je te laisse faire les essais, tu vas vite comprendre le problème.
  • Paisible.frPaisible.fr Membre
    09:01 modifié #4
    dans 1229328353:

    La NSView contient-t-elle l'Outline View ? C'est pour comprendre.


    Oui mon OutlineView est DANS la NSView.

    Le temps de calcul est maintenant "ridicule", ce qui me prend du temps c'est le peuplement de l'outlineview.
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #5
    dans 1229358917:

    Le temps de calcul est maintenant "ridicule", ce qui me prend du temps c'est le peuplement de l'outlineview.


    Je ne comprends pas. On ne peuple pas une outline view. C'est l'outline view qui demande le contenu à  sa data source, au fur et à  mesure des besoins d'affichage (demandé par l'utilisateur).
    Donc peut-être que ton modèle dans le data source est long à  calculer, mais en aucun cas cela ne doit être l'affichage qui prenne du temps. Si c'est ce temps de calcul dont tu parles, suivre l'indication de No

    Voir peut-être ici
  • Paisible.frPaisible.fr Membre
    09:01 modifié #6
    Merci de vos réponse No, Philippe49 et Céroce.

    Après avoir pris le temps de la réflexion je pense que la solution est de passer par un Thread dans mon cas.

    Du coup à  la pause entre midi et deux, je me suis plongé ni une ni deux sur mon "Aaron Hillegass" pour voir comment on programme ces choses en Cocoa. Et là  grosse stupéfaction : pas d'entrée sur NSThread dans l'index  ! Je me rabat illico presto sur la table des matière en quête d'un chapitre ayant trait à  la question. Rien, si ce n'est le dernier chapitre sur les NSTask qui me semble pas correspondre à  priori. Mais je jette un oeuil (je suis curieux hein ?) et je constate que le chapitre est très light. Mais je commence là  lecture, et je retrouve le sourire : ca parle de multi-thread ! Cependant, ca dure pas longtemps juste quelques lignes. Apparemment, si je résume la pensée de Aaron, il semble qu'il préfère le multi-process au multi-threading : "c'est moins glamour mais plus utilisable" (traduction libre par ma part)

    Son chapitre sur le "multi-process" semble pourvoir m'aider à  résoudre mon problème, donc je vais l'étudier un peu plus. En parallèle je vais regarder un peu la doc d'Apple sur NSThread et le guide qui l'accompagne. A première vu : vaux mieux lire à  tête reposée ! (Si quelqu'un à  un tuto à  proposer je prends)

    Et vous en pensez quoi vous de "multithreading vs multiprocessing" comme dit "Hillegass" ?
    Dans l'optique de Leopard Snow des impacts sont-ils à  prévoir vu que l'un des apports majeurs de cette version tourne autour de ces questions de threadings/processus/processeurs/co-processeurs, etc ... ?

  • CéroceCéroce Membre, Modérateur
    09:01 modifié #7
    Le multi-processus n'est pas la solution ici. Pour mieux comprendre ce qu'est un processus, tu obtiens leur liste en tapant la commande top dans le terminal.

    Ce que tu veux, c'est lancer une tâche en parallèle, dans ton appli, et pouvoir échanger des données entre elles. Il faut vraiment utiliser un thread ici. Il n'est pas étonnant que ce ne soit pas décrit dans le livre d'Hillegass, car ça devient un sujet technique. Je te conseille d'aller lire un peu Wikipédia pour comprendre ce que sont les threads, et les problèmes liés à  leur utilisation.
  • Philippe49Philippe49 Membre
    09:01 modifié #8
    Un schéma simpliste de lancement de NSThread

    -(void) startThread:(id) sender{
    requester=[sender retain];  // on peut en avoir besoin
    [NSThread detachNewThreadSelector:@selector(compute:) toTarget:self withObject:nil];
    }

    -(void) compute:(id)object {
    NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];

    // do anything

    [pool release];
    }
  • Paisible.frPaisible.fr Membre
    09:01 modifié #9
    dans 1229627838:

    Un schéma simpliste de lancement de NSThread

    -(void) startThread:(id) sender{
    requester=[sender retain];  // on peut en avoir besoin
    [NSThread detachNewThreadSelector:@selector(compute:) toTarget:self withObject:nil];
    }

    -(void) compute:(id)object {
    NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];

    // do anything

    [pool release];
    }



    Merci Philippe49,

    J'ai fait quelque tests et cela semble répondre à  mes besoins mais j'ai un problème de mémoire je pense.

    Au départ j'avais ça :
    -(void)awakeFromNib<br />{<br />	[self compute:nil];<br />}<br />-(void) compute:(id)object {<br />	NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];<br /><br />	// do anything<br /><br />	[pool release];<br />}
    


    Cela ne posait visiblement pas de probleme memoire...

    J'ai transformé en pour threader le "compute" qui est long :
    -(void)awakeFromNib<br />{<br />	[NSThread detachNewThreadSelector:@selector(compute:) toTarget:self withObject:nil];<br />}<br />-(void) compute:(id)object {<br />	NSAutoreleasePool * pool =[[NSAutoreleasePool alloc] init];<br /><br />	// do anything<br /><br />	[pool release];<br />}
    


    Et là  je me retrouve avec l'erreur suivante :
    *** _NSAutoreleaseNoPool(): Object 0x127b30 of class NSCFString autoreleased with no pool in place - just leaking<br />Stack: (0x90a4973f 0x90955e32 0x9096128a 0x45c1 0x9095c7ed 0x9095c394 0x93112095 0x93111f52)<br /><br />
    

    Notez que je n'ai pas fait le "retain" recommandé (pas trouvé comment faire)
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #10
    Un petit essai pour expliquer ton problème :

    @implementation AppDelegate
    -(void) awakeFromNib {
    NSLog(@awaking);
    }
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSLog(@finish launching);
    }
    @end


    [Session started at 2008-12-22 07:41:58 +0100.]
    2008-12-22 07:41:58.926 Untitled2[283:10b] awaking
    2008-12-22 07:41:59.048 Untitled2[283:10b] finish launching


    Et la conclusion : Ne pas lancer de thread dans la méthode awakeFromNib. Attendre que l'application soit complètement installée.
  • Philippe49Philippe49 Membre
    09:01 modifié #11
    dans 1229627838:

    Un schéma simpliste de lancement de NSThread

    [NSThread detachNewThreadSelector:@selector(compute:) toTarget:self withObject:nil];


    L'argument après withObject: est celui qui est envoyé à  la méthode lors du lancement du thread.
    Il peut être  nil comme ici, mais il peut aussi être n'importe quel objet selon les besoins.

  • Paisible.frPaisible.fr Membre
    décembre 2008 modifié #12
    dans 1229928182:

    Et la conclusion : Ne pas lancer de thread dans la méthode awakeFromNib. Attendre que l'application soit complètement installée.

    Comment est-il possible de savoir que l'application est complètement installée ?

    Merci encore pour toutes ces explications très pédagogiques et détaillées.
    Je pense que cela mérite, selon la tradition du forum, que je t'offre un coup à  boire virtuel pour te remercier.  :p (Si tu passe dans le sud-est de la France je le matérialiserait bien volontier  ;) )
  • Philippe49Philippe49 Membre
    09:01 modifié #13
    dans 1229930669:

    Comment est-il possible de savoir que l'application est complètement installée ?

    • Soit tu déclares une classe en delegate de l'application : dans le nib, connecter l'IBoutlet de "Application" à  un de tes objets "cube bleu", et alors tu peux implémenter  - (void)applicationDidFinishLaunching:(NSNotification *)aNotification  dans la classe correspondante. Cette méthode est envoyée au delegate de l'application quand l'installation est terminée.
    • Soit tu attends que l'interface soit utilisable, et tu lances le thread suite à  une action sur l'UI (un bouton, un control quelconque, ...) 

    dans 1229930669:

    Merci encore pour toutes ces explications très pédagogiques et détaillées.
    Je pense que cela mérite, selon la traduction du forum, que je t'offre un coup à  boire virtuel pour te remercier.  :p (Si tu passe dans le sud-est de la France je le matérialiserait bien volontier  ;) )



    Avec le post d'hier en plus, Je crois vraiment que je vais finir par aller (re)faire un tour dans cette région !!  :p
  • Paisible.frPaisible.fr Membre
    09:01 modifié #14
    J'ai déclaré en delegate dans mon AppControler un  :
    <br />- (void)applicationDidFinishLaunching:(NSNotification *)aNotification<br />{<br />	NSLog(@&quot;start&quot;);<br />}<br />
    


    Ca fonctionne bien.

    Si je rajoute :
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification<br />{<br />	NSLog(@&quot;start&quot;);<br />	<br />	[NSThread detachNewThreadSelector:@selector(_compute:) toTarget:[mMainWindowController getIndexViewController] withObject:nil];<br />}
    


    J'ai la même erreur :
    <br />*** _NSAutoreleaseNoPool(): Object 0x127b30 of class NSCFString autoreleased with no pool in place - just leaking<br />Stack: (0x90a4973f 0x90955e32 0x9096128a 0x45c1 0x9095c7ed 0x9095c394 0x93112095 0x93111f52)
    


    Alors que si je fais :
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification<br />{<br />	NSLog(@&quot;start&quot;);<br />	<br />	[[mMainWindowController getIndexViewController] _compute];<br />}<br />
    

    Cela fonctionne.

    Donc, je serais tenté de dire que l'histoire de ne pas lancer de thread dans la méthode awakeFromNib et d'attendre que l'application soit complètement installée n'était pas vraiment le problème.
  • Philippe49Philippe49 Membre
    décembre 2008 modifié #15
    dans 1230455253:

    J'ai la même erreur :
    <br />*** _NSAutoreleaseNoPool(): Object 0x127b30 of class NSCFString autoreleased with no pool in place - just leaking<br />Stack: (0x90a4973f 0x90955e32 0x9096128a 0x45c1 0x9095c7ed 0x9095c394 0x93112095 0x93111f52)
    


    Mais quelle peut bien être cette chaà®ne dont le message d'erreur parle ?
    Tu utilises une chaà®ne de caractères dans ce programme ?

    dans 1230455253:

    Alors que si je fais :
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification<br />{<br />	NSLog(@&quot;start&quot;);<br />	<br />	[[mMainWindowController getIndexViewController] _compute];<br />}<br />
    

    Cela fonctionne.

    Mais là  tu ne lances pas de thread ...

    dans 1230455253:

    Donc, je serais tenté de dire que l'histoire de ne pas lancer de thread dans la méthode awakeFromNib et d'attendre que l'application soit complètement installée n'était pas vraiment le problème.

    Comme les threads dans un programme partagent les ressources, il semble de toutes façons plus  prudent d'attendre que l'application ait complètement fini son installation avant de lancer un thread "à  l'aveugle" comme le fait ce detachNewThreadSelector: toTarget: withObject: .  
  • Philippe49Philippe49 Membre
    09:01 modifié #16
    Ci-joint une mini-appli d'essai de lancement de thread
  • Paisible.frPaisible.fr Membre
    09:01 modifié #17
    Je pense avoir trouvé.

    En fait je n'utilise pas un :
    <br />NSLog(@&quot;start&quot;);<br />
    


    Mais un :
    <br />DebugLog(@&quot;start&quot;);<br />
    

    Pour avoir un formatage plus complet.

    Implémenté de la façon suivante :
    <br />#define DEBUG_LOG<br /><br />#ifdef DEBUG_LOG<br />#define DebugLog(log,...) NSLog(@&quot;%s:%d &#092;&quot;%@&#092;&quot;&quot;, __PRETTY_FUNCTION__, __LINE__,[NSString stringWithFormat:log,##__VA_ARGS__])<br />#else<br />#define DebugLog(log,...)<br />#endif<br />
    


    En retirant cette simple ligne tout à  l'air d'être rentrer dans l'ordre.
    Je vais pousser l'investigation pour m'en assurer.
  • Paisible.frPaisible.fr Membre
    09:01 modifié #18
    Je confirme. Cela paraissait évident, mais dans l'action...
    Reste à  corriger mon DebugLog  :'(

    Merci beaucoup à  tous. 
Connectez-vous ou Inscrivez-vous pour répondre.