NSProgressIndicator sous El Capitan
Bonjour,
Je reviens vers vous après une longue période de codage sans anicroche. Mais j'ai un truc agaçant: un NSProgressIndicator qui a cessé de fonctionner depuis El Capitan.
Plus exactement, l'interface n'est plus remise à jour du tout durant un processus de traitement de fichier: le NSProgressIndicator n'est que la partie visible d'un iceberg qui concerne toute la fenêtre.
Le traitement de fichiers s'effectue par lot, dans une boucle. L'interface n'est réactualisée qu'à la sortie de la boucle, avec les valeurs finales correctes. Un NSLog me montre que tout se déroule bien dans la boucle.
J'ai essayé le dessin différé (setNeedsDisplay), immédiat (display), de passer par une animation... rien n'y fait. J'ai adressé un rapport de bug qui s'est révélé un duplicata. Mais peut-être ai-je manqué une marche? il me semble incroyable de devoir écrire une dizaine le lignes de code pour faire avancer un indicateur...
D'avance merci si vous avez une façon de contourner le problème en attendant qu'Apple corrige (ou documente) ce nouveau comportement.
Réponses
Tu l'as passé en multi-threadé via la propriété usesThreadedAnimation?
Non, tout est sur le main thread.
@Mala: je ne trouve pas la doc claire au sujet de .usesThreadedAnimation. Elle parle de l'animation, pas du rafraichissement. De ce que j'en comprends, ça voudrait dire que l'animation de l'état "indéterminé" se poursuivrait, mais pas que changer sa valeur se reflèterait.
As-tu l'expérience là -dessus ?
Il y a une chose qui m'échappe: si le travail effectué sur les fichiers ET la mise à jour de l'indicateur sont sur le même thread, pourquoi l'interface ne s'actualise-t-elle pas?
La runloop est une boucle qui:
1) reçoit les événements
2) donne la main à ton code pour y réagir
3) redessine les vues en appelant leur méthode -drawRect, si l'étape 2 a signalé une demande de rafraichissement avec setNeedsDisplay:
Si tu lances un traitement long dans 2), alors c'est normal qu'il n'y ait pas de mise à jour de l'IHM.
Une solution est de donner la main à la runloop grâce à -[NSRunLoop runUntilDate:]. Mais une bien meilleure solution est de faire tes traitements sur des threads secondaires. Ce n'est pas forcément très compliqué, par exemple en utilisant NS(Block)Operation.
C'est quoi exactement ton traitement de fichier ?
Bonjour et merci à vous deux pour vos réponses.
De la réduction d'image jusqu'à concurrence d'une taille à ne pas dépasser. C'est plus simple de mettre le code:
La mise à jour des indicateurs (progression, nom du fichier en cours de traitement, fichiers restants) s'effectue à l'intérieur d'une boucle for..in. La valeur percentage est donnée par les préférences, c'est un coefficient de réduction à appliquer à chaque itération.
Ce que je ne comprends pas, c'est que le traitement lui-même (la boucle interne do...while) devrait "rendre la main" à la boucle externe (for...in) et mettre à jour l'affichage. C'est ce qu'elle faisait sous Yosemite et Mavericks.
Ton code bloque clairement la runloop.
Je t'ai donné la solution, tu n'as plus qu'à tester avec [NSRunLoop runUntilDate:], par exemple au bas de ta boucle for...in .
Il force un display. C'est crade mais ça marchait effectivement jusqu'à présent.
Le fait de passer usesThreadedAnimation à YES doit suffire de mémoire car le changement de valeur fait un setNeedsDisplay de facto. Enfin il me semble. De toute façon, il a une ligne de code à ajouter pour vérifier donc autant commencer par là .
@ Céroce: OK, ça fonctionne effectivement. Mais en admettant que je veuille faire les choses proprement:
- un fil pour la boucle de traitement ?
- un fil par traitement ?
- comment le(s) fil(s) indiquent-ils au fil principal l'avancement de travaux, pour que l'indicateur se mette à jour ?
@ Mala: oui, c'est crade, mais si je balance des setNeedsDisplay à la queue-leu-leu, l'indicateur va se mettre à jour une seule fois, c'est-à -dire quand il sera arrivé à la fin du traitement, non?
Bon,
[self.indicator setUsesThreadedAnimation:YES];
tu peux utiliser un truc dans le genre:
Je fais ça sans doc donc t'as l'idée en gros mais ça devrait fonctionner.
Normal. Céroce a expliquer le pourquoi.
Et en passant la barre de progression en mutli-threadé, tu n'as ainsi pas à te soucier du refresh. C'est historiquement fait pour ça.
ça dépend de comment tu souhaites informer de l'avancement. Disons que tu veux que la barre progresse à chaque fichier traité, voici comment je m'y prendrais, je crois:
- créer une NSOperationQueue
- créer une NSBlockOperation par fichier à traiter.
- fixer leur propriété .completionBlock. C'est ce qui permet de savoir que le traitement d'un fichier est terminé
- quand le completionBlock est appelé, changer la valeur du progress indicator. Attention, c'est à faire sur le thread principal. Il faut aussi regarder le nombre d'opérations restantes dans la queue. Si c'est zéro, alors c'est terminé.
La propriété NSOperationQueue.maxConcurrentOperationCount peut rester à sa valeur par défaut. Dans ce cas, c'est OS X qui détermine le nombre de threads à créer. Les opérations de traitement des fichiers vont donc s'exécuter en parallèle, et ça ira plus vite qu'avant. Si tu mets la propriété à 1, alors tu as une queue série, avec un seul thread secondaire.
Je vois un problème plus que potentiel dans ton code:
Les opérations s'exécutant en parallèle, ça va mettre un gros souk dans les lockFocus et unlockFocus. Tu n'auras aucune idée de si currentContext correspond bien à l'image traitée.
Personnellement, je règlerais ça en utilisant un CGBitmapContext. Ainsi, il n'y a pas d'histoire de "contexte courant", puisqu'on passe le contexte en paramètre à chaque fois, et on évite tout problème.
(En passant: quand on dit qu'il ne faut pas utiliser de singleton, comme ici [NSGraphicsContext currentContext], il y a une bonne raison. En fait, plusieurs).
Pyroh proposait de passer par GCD plutôt que par NSOperation/Queue, c'est une option à considérer, pouvant rendre le code plus simple.
Mon Dieu, je finis avec des trucs comme ça:
Mais au moins je n'ai plus ce fichu ballon de plage...
Merci pour ces conseils, j'ai dû pas mal ramer dans la doc, sur SO et autres...