NSSavepanel et modaldelegate
tablier
Membre
je souhaite gérer la sortie d'un "savepanel" par "beginSheetForDirectory:..." Le n'arrive pas à repasser le context:
Si cela marchait comme c'est expliqué dans la doc, truc devrait contenir YES,NO,NO,YES,NO. Or il contient YES,YES,YES,YES,YES.
Je constate que Kontext contient bien l'adresse de leContexte, c'est à dire que l'adresse de l'array est bien transmise, mais que le contenu a disparu!! J'ai donc relus la doc sur la gestion de la mémoire et les réservations, je ne trouve pas ou est mon erreur!!
Allons jusqu'au bout: ces mèthodes font parti d'un objet alloué et initialisé sous AppleScript Studio par
if (deleguer = missing value) then
set deleguer to (call method "initialise" of object (call method "alloc" of class "deleguer"))
end if
puis saveItem: est appelé par un call method
Quelqu'un aurait une idée de l'erreur que je fait? (ou de ce que je n'ai pas compris).
- (NSArray *)saveItem:(NSString *)leChemin :(NSString *)leMessage :(NSString *)leTitre :(NSString *)leLabel :(NSString *)letype<br />{<br />int retour ;<br />NSSavePanel *sauvePanel ;<br />NSString *filepath, *lefichier ;<br />BOOL leContexte[5] ;<br /><br /> leContexte[0] = YES ; leContexte[1] = NO ; leContexte[2] = NO ; leContexte[3] = YES ; leContexte[4] = NO ; <br /> <br /> filepath = [leChemin stringByDeletingLastPathComponent] ;<br /> lefichier = [[[leChemin stringByDeletingPathExtension] stringByAppendingPathExtension:letype] lastPathComponent] ;<br /> <br /> if (!k2)<br /> {<br /> sauvePanel = [NSSavePanel savePanel] ;<br /> [sauvePanel setCanSelectHiddenExtension:YES] ; <br /> [sauvePanel setCanCreateDirectories: k0] ;<br /> [sauvePanel setDirectory:leChemin] ;<br /> [sauvePanel setMessage:leMessage ];<br /> [sauvePanel setTitle:leTitre ];<br /> [sauvePanel setNameFieldLabel:leLabel] ;<br /> [sauvePanel setRequiredFileType: letype] ;<br /> <br /> [sauvePanel beginSheetForDirectory:filepath file:lefichier modalForWindow:nil modalDelegate:(self) <br /> didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:leContexte ] ;<br /><br /> filepath = [sauvePanel filename]; // Prise du nom<br /> <br /> .................................................................etc<br /><br /> return [NSArray arrayWithObjects: ............................., nil] ;<br />}<br /><br />- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo<br />{<br />BOOL *Kontext, truc[5] ;<br /><br /> Kontext = (BOOL *)contextInfo ;<br /> truc[0] = *Kontext++ ; truc[1] = *Kontext++ ; truc[2] = *Kontext++ ; truc[3] = *Kontext++ ; truc[4] = *Kontext++ ;<br /> <br /> .................................. etc<br />}<br />
Si cela marchait comme c'est expliqué dans la doc, truc devrait contenir YES,NO,NO,YES,NO. Or il contient YES,YES,YES,YES,YES.
Je constate que Kontext contient bien l'adresse de leContexte, c'est à dire que l'adresse de l'array est bien transmise, mais que le contenu a disparu!! J'ai donc relus la doc sur la gestion de la mémoire et les réservations, je ne trouve pas ou est mon erreur!!
Allons jusqu'au bout: ces mèthodes font parti d'un objet alloué et initialisé sous AppleScript Studio par
if (deleguer = missing value) then
set deleguer to (call method "initialise" of object (call method "alloc" of class "deleguer"))
end if
puis saveItem: est appelé par un call method
Quelqu'un aurait une idée de l'erreur que je fait? (ou de ce que je n'ai pas compris).
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
(passe le en variable de classe)
C'est donc, en effet, pourquoi le tableau leContexte[5]) n'existe plus au moment quand il tente de fermer le savePanel.
Plus qu'une variable de classe, il peut aussi utiliser un objet (non-autorelease bien sûr) comme paramètre à contextInfo.
.
Effectivement la méthode n'est pas bloquante (je ne l'avais pas vu!) mais il y a quand même quelque chose que je ne comprend pas! :-\\
En C, si l'on appelle une routine B a l'intérieur d'une routine A, la routine A ne peut se terminer (sauf exception) tant que l'exécution de la routine B n'est pas terminée. Et les variables définies au début de A restent valables jusqu'à son 'return' final: donc durant toute l'exécution de B.
Vous avez l'air de dire qu'ici en Objective-C ce n'est pas le cas. Pourriez-vous être plus explicites ou me donner un lien sur la partie de la doc qui explique cela.
regarde du côté de NSThread
Dans tous les cas, si une méthode A appelle une méthode B, on revient puis termine la méthode A qu'une fois que la méthode B est terminée... Mais la méthode B n'est pas forcément bloquante pour autant.
Tu peux par exemple avoir une méthode qui demande l'affichage d'un SavePanel, et te rend la main. Il faut bien attendre que la méthode qui affiche le SavePanel soit terminée, mais elle se termine une fois que le SavePanel a été affiché, sans attendre que l'utlisateur ait cliqué sur OK. Donc elle se termine très vite, et te rend la main dans ta méthode principale très vite.
Plus tard, quand l'utilisateur a cliqué sur OK, le SavePanel appelle une autre méthode, pour t'indiquer que le panel s'est fermé. Mais c'est donc séparé de ton appel pour afficher le SavePanel.
Donc dans un sens tu n'as pas tord, ça marche comme en C, il faut attendre que la méthode appelée soit terminée pour reprendre la main, mais il y a des méthodes qui se contentent d'initier l'action (afficher un SavePanel), et c'est tout, donc qui sont plus courtes que ce que tu pourrais penser. D'ailleurs tu peux tout à fait faire ça en C aussi, du moment que tu utilises des threads aussi.
Dans le contexte évoqué ici, le multhreading c'est vraiment de l'overkill pour expliquer ce comportement: c'est de la programmation événementielle de base.
Comme Ali l'a signalé, une méthode non bloquante comme celle-ci se contente d'afficher une fenêtre, et puis on rentre dans un cycle normal de gestion événement/attente d'événement (qui est le cas par défaut, pour avoir du bloquant lorsque des éléments d'interface comme ceux-ci sont impliqués, ça doit vraiment être intentionnel).
Lorsque l'utilsateur pousse sur OK ou Annuler (bête paradigme action/target qu'on a tout le temps dans Cocoa), le Save Panel se contente d'appeller un sélecteur (qui peut-être stocké comme variable d'instance) sur un objet (qui peut-être lui aussi stocké de cette manière) - enfin, dans le cas présent c'est plutôt via une NSInvocation, mais ça ne change rien au principe.
Pour tablier: c'est donc le genre de trucs que tu fais toi-même lorsque tu écris un programme. N'oublie pas que Apple est soumis aux mêmes règles que toi, et qu'ils seraient stupides de ne permettre que le cas le plus contraignant (qui est le bloquant ici), alors que ce qui est permis "naturellement" est beaucoup souple: le non-bloquant n'exclut pas le bloquant, mais la réciproque n'est pas vraie.
Pour Ali: l'asynchrone a également tout son sens dans de la programmation événementielle mono threadée.
J'ai failli parler de synchrone / asynchrone, mais je trouve ça embrouillant, car "synchrone" veut dire "Qui a lieu en même temps." et là c'est l'idée inverse de ce qu'on veut faire passer.
Que veut dire "overkill" ? ???
Les threads sont à la base de tout ça, je ne vois pas le problème ?
Je rappelle quand même que la question était :
Sinon, overkill = mise en oe“uvre de moyens beaucoup trop importants par rapport à l'objectif à atteindre (sous entendu, on peut le faire par les threads, mais inutile d'aller jusque là pour faire la même chose).
Ou bien alors tu transformes légèrement ta définition par une définition "factuelle", et tu considères que le résultat d'une méthode/fonction synchrone est exploitable directement (que ce soit par la valeur renvoyée et/ou une valeur passée en référence), tandis que le résultat d'une fonction asynchrone est "communiqué" par un callback/méthode de délégué, et dans ce cas il devient clair qu'on a affaire à de l'asychrone.
Il suffit de se placer dans le debugger pour le voir.
À partir du moment où tu as deux choses qui peuvent se faire en même temps, il y a multi-threading.
Or sa question était basée là dessus, donc je l'oriente vers la classe NSThread, ça me parait normal  :-\\
S'il n'y avait pas multi-threading, la "callback" ne pourrait pas être appelée après que la fonction ne soit terminée...
Le "callback" peut très bien être appelé suite à un événement bien précis (en l'occurence, dans le cas présent, suite au mouseUp sur le bouton OK ou Annuler du save panel, mais pour le processeur, il n'y a jamais qu'une seule chose à la fois qui se passe et donc le multhreading est inutile pour ce genre de cas).
Dans ce cas c'est une méthode dite "asynchrone" ; elle est bloquante pour l'application.
(ça existe... Toutes les fonction "beginModalSession...")
Ici, si sa fonction "savePanelDidEnd" a été appelée après la fin de sa fonction "saveItem", c'est qu'il y avait forcément un second thread. C'est absolument impossible autrement (ou alors il faut m'expliquer...)
Lorsque tu appelles [tt]beginSheetForDirectory:file:[...]contextInfo:[/tt], le save panel affiche le panneau, le mets au premier plan et stocke dans des variables d'instance le modalDelegate et le didEndSelector. Lorsque l'utilisateur pousse sur OK, le code correspondant pour le save panel serait: [tt][_modalDelegate performSelector:_didEndSelector withObject:self];[/tt]
Pas compliqué non?
Une application toute bête qui affiche un NSSavePanel ; tu me diras donc qu'elle n'est pas "multi-treadée"
Place un break-point sur le NSLog de la première fonction, tu verras que ce n'est pas 1, mais 3 threads que tu as dans cette application.
http://cschlum2.free.fr/TestThread.zip
Les threads sont à la base de tout ce qui est exécuté en même temps, donc ici c'est obligatoire. La fonction se termine en même temps que le NSSavePanel s'affiche !
D'ailleurs, modifie tes logs pour y ajouter l'info suivante: [NSThread isMultiThreaded]?@threaded:@not threaded
Tu auras dans les 2 cas "not threaded".
Ils n'utilisent pas la classe NSThread pour détacher leurs threads tout simplement.
La question de base était comme tu me l'as rappelé:
Je réponds dans le cadre de cette question, et reste dans le cadre cette question.
Maintenant si l'implémentation Tiger de l'AppKit crée 3 threads parce qu'il y a des animations, ou des observers divers pour que l'affichage du save panel reste à jour, ou des threads qui chargent les icônes des fichiers, ou... (qui n'est plus qu'un nib qui est simplement chargé, au cas où tu l'aurais oublié). ça ne veut pas dire que les threads en questions sont utilisés dans la gestion des événements et du problème évoqué ici.
La question était justement de savoir pourquoi la fonction avait poursuivi après l'appel de "beginSheetForDirectory" et j'y répond, c'est tout.
Explique moi, comment, sans thread, le processeur peut partir dans "beginSheetForDirectory", afficher la sheet et attendre que l'utilisateur clique sur un bouton et en même temps poursuit dans la fonction qui a appelé "beginSheetForDirectory".
Pas la peine de dire que je suis fatiguant, on peut discuter calmement "en adultes" comme ça a été fait jusque là ...Â
Voilà ce que j'essaie de dire depuis tout à l'heure...
-- Le programmeur ne peut pas savoir à priori si les méthodes appellées (qui sont dans les frameworks) utilisent un (ou plusieurs) thread(s) différent(s) du thread principale de l'application.
-- La seule maniére d'avoir le renseignement est de mettre des points d'arrêts aux endroits appropriés.
Est-ce que je me trompe ?
Maintenant l'appli là où je ne te suis absolument pas est que l'appli n'attend pas que l'utilisateur clique sur OK et continue l'exécution de la méthode qui appelé beginSheetForDirectory en même temps. Elle termine l'exécution de la méthode qui a appelé beginSheetForDirectory et rentre dans un nouveau cycle normal de runloop vu que la dite fonction n'est pas bloquante. À part la question de l'animation, tout peut parfaitement se faire dans le même thread. Mais dans le cas où tu as une fonction bloquante, il faut effectivement un second thread (mais ce n'est pas le cas ici).
Non, les points d'arrêt n'apprennent rien ; sur ce point il a raison, les threads peuvent très bien être utilisés pour des routines d'affichage.
Donc je ne vois pas comment montrer par l'exemple qu'il y a plusieurs threads, c'est simplement la logique qui le dit dans ce cas.
Oui, je suis d'accord, le problème n'est pas l'animation ; oublions l'exemple foireux qui ne prouve rien donc.
Le problème (sa partie intégrante même), c'était de savoir pourquoi la fonction continuait avant l'appel de la "callback" et la réponse à ce problème c'est "Thread".
Du coup, je ne vois pas pourquoi on s'énerve comme ça...
Supprimons l'animation ; imaginons qu'au lieu d'afficher un NSPanel l'application parte en boucle infinie en attendant que l'utilisateur tape "y".
Comment veux-tu avec simplement un fonctionnement de callback il y ait deux choses de faites à la fois ?
Alors multi-thread obligatoire ou non? :brule:
Aller je prends le parti de Renaud. Une application peut parfaitement être monothread et gérer ce genre de panneau. Dans une application graphique, le main thread n'est jamais qu'un gestionnaire d'événements. Rien ne l'empêche d'afficher une nouvelle fenêtre, un nouveau panneau ou tout ce qu'on veut dans une méthode prévue à cet effet puis de revenir comme si de rien n'était à sa boucle d'événements. La validation du panneau ne fera que générer un évènement dans le thread principal.
La méthode affiche le panel et continue. Je ne vois pas où on a forcément besoin d'un second thread.
Et qui gère l'attente d'une action de l'utilisateur dans ce cas ? Dieu ? :P
Le processeur fait forcément deux chose à la fois à un moment... Attendre en boucle une réponse, et continuer la fonction précédente.
Dans le cas d'une fonction bloquante, oui, il faut un second thread. Dans le cas d'une fonction non bloquante, il ne faut pas de second thread: la méthode qui appelle beginSheetForDirectory continue son exécution normalement et un nouveau cycle de runloop commence. Simplement comme il y a une sheet sur la fenêtre, l'application ne fait pas suivre les événements qui auraient dus arriver à la fenêtre parent, mais le cas évoqué n'a rien de modal (au sens NSApp du terme), et donc il n'est pas nécessaire de créer des threads en plus.
C'est quand la fonction est non bloquante qu'il faut un thread justementÂ
Parce qu'à ce moment, un nouveau thread s'occupe de la fonction appelée tandis que l'ancien poursuit la fonction qui l'a appelée
Tu fais à mon avis l'erreur de penser que beginSheetWithDirectory fait plus que ce qu'elle fait réellemement: elle ne fait qu'instancier une fenêtre et l'afficher, rien de plus.
Dans le cas où tu as une fonction bloquante (genre une fenêtre modale justement), il faut créer un nouveau thread pour gérer les événéments de manière à laisser le thread en cours arrêté.