Comment savoir qd une NSView est affichée ?
Paisible.fr
Membre
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 ?
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 ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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.
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.
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.
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
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 ... ?
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.
-(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 :
Cela ne posait visiblement pas de probleme memoire...
J'ai transformé en pour threader le "compute" qui est long :
Et là je me retrouve avec l'erreur suivante :
Notez que je n'ai pas fait le "retain" recommandé (pas trouvé comment faire)
@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.
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.
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. (Si tu passe dans le sud-est de la France je le matérialiserait bien volontier )
• 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, ...)
Avec le post d'hier en plus, Je crois vraiment que je vais finir par aller (re)faire un tour dans cette région !!
Ca fonctionne bien.
Si je rajoute :
J'ai la même erreur :
Alors que si je fais :
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.
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 ?
Mais là tu ne lances pas de thread ...
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: .Â
En fait je n'utilise pas un :
Mais un :
Pour avoir un formatage plus complet.
Implémenté de la façon suivante :
En retirant cette simple ligne tout à l'air d'être rentrer dans l'ordre.
Je vais pousser l'investigation pour m'en assurer.
Reste à corriger mon DebugLog
Merci beaucoup à tous.