Créer system event Cocoa

ettiboettibo Membre
février 2012 modifié dans Vos applications #1
Bonjour à  tous,



dans pas mal de classes cocoa, je vois des systèmes d'évents, mais est-il possible de créer des events interclasse, je m'explique.

Dison qu'une classe que l'on va appelé papa contient une classe que l'on va appeler fiston.

Le papa fait sa vie et de temps en temps doit faire effectuer des opérations asyncrhones au fiston.

Le fiston ne sait pas qu'il a un Papa (je dit ça car je voudrais éviter la double inclusion), mais voudrait signaler à  tout le monde haut et fort qu'il a fini son boulot.

Et donc le papa doit être en capacité de savoir que son fiston a fini.



Je pens passer par le système de NSNotification.



J'éspère avoir été clair.



Merci d'avance de vos réponses.
Mots clés:

Réponses

  • Hello,

    Utiliser les NSNotications me semble approprié, oui.
  • Et au niveau architecture, ça ne parait pas incohérent?
  • KixxxKixxx Membre
    février 2012 modifié #4
    Pourquoi ne pas utiliser un protocole ? (Je dis ça comme ça, ce n'est peut-être pas une bonne solution donc j'aimerai bien en savoir plus aussi...cela me permet de m'améliorer ^^)
  • AliGatorAliGator Membre, Modérateur
    février 2012 modifié #5
    Bah non au contraire ça me parait bien car vu que tu sembles souhaiter un couplage faible et éviter le couplage fort, les NSNotifications correspondent à  ton
    [font=helvetica, arial, sans-serif]Le fiston [...] voudrait signaler à  tout le monde haut et fort qu'il a fini son boulot
    .[/font]



    [font=helvetica, arial, sans-serif]L'autre solution serait que le fiston ait un delegate, ce qui permet de lui assigner un objet à  qui signaler qu'il a fini. Cet objet tu t'en fiches de qui il est (en l'occurrence ce sera le Papa mais bon) du moment qu'il sait répondre à  l'information qd on lui signale.[/font]

    [font=helvetica, arial, sans-serif]
    // juste déclarer que la classe existe pour que le compilo gueule pas qd il rencontre le paramètre de type &quot;Fiston&quot; ci-dessous alors qu&#39;il ne le connaà®t pas encore<br />
    @class Fiston;<br />
    <br />
    // Déclarer le protocole auquel devra répondre le delegate. Permet le couplage faible. N&#39;importe quel objet qui implément cette méthode pourra se prétendre delegate du Fiston et être informé du coup quand le fiston a fini<br />
    @protocol FistonDelegate<br />
    -(void)fistonDidFinish:(Fiston*)leFiston;<br />
    @end<br />
    <br />
    // Et déclarer la classe Fiston, avec une propriété &quot;delegate&quot; dont on se fiche du type du moment qu&#39;il se conforme au protocole<br />
    @interface Fiston : NSObject<br />
    ...<br />
    @property(assign) id&lt;FistonDelegate&gt; delegate; // Le delegate peut être n&#39;importe quoi, un Papa ou autre chose, on demande juste qu&#39;il implémente la méthode fistonDidFinish.<br />
    @end
    
    Et pour la classe Papa :
    // Et à  côté quand on déclare la classe Papa, on indique juste que Papa se conforme au protocole FistonDelegate<br />
    @interface Papa : NSObject&lt;FistonDelegate&gt;<br />
    ...<br />
    @end
    
    Du coup quand tu crées le Fiston, tu lui affectes l'objet Papa à  sa propriété "delegate", et quand le Fiston doit indiquer à  son delegate qu'il a fini, il appelle [tt][self.delegate fistonDidFinish:self];[/tt] ce qui va donc appeler cette méthode sur le Papa (en passant le Fiston en paramètre, au cas où, ça peut toujours servir). Avantage : dans toute cette procédure, le Fiston n'a pas besoin de connaà®tre la classe Papa. Pour lui il envoie juste un message à  son delegate, quelle que soit la classe de ce delegate. D'où couplage faible.[/font]



    [font=helvetica, arial, sans-serif]Bref, cette solution du delegate est un intermédiaire, puisque le Fiston doit avoir un objet à  qui signaler qu'il a fini, même si ça reste du couplage faible car il n'a pas besoin de savoir que cet objet est de type Papa.[/font] [font=helvetica, arial, sans-serif]Encore faut-il affecter à  un moment donné l'objet de type Papa à  son Fiston, même si le Fiston ne sait pas de quel type d'objet il s'agit.[/font]



    [font=helvetica, arial, sans-serif]Du coup si tu procèdes ainsi, le Fiston ne va pas "crier haut et fort qu'il a fini à  tout le monde", mais "dire à  qqun en particulier qu'il a fini, sans se soucier de savoir qui est ce quelqu'un".[/font]



    [font=helvetica, arial, sans-serif]Alors que si tu préfères les NSNotifications, qui sont du coup tout aussi bien pour ton cas, le Fiston va crier à  tout le monde, et c'est à  la charge du Papa d'écouter, de dire que ça l'intéresse, et d'intercepter donc la notification, sans que le Fiston n'en sache rien. Et, autre petite subtilité, plusieurs objets (Papa ou autre) peuvent s'abonner à  la notification, là  où dans la solution delegate c'est plutôt prévu pour qu'un objet unique soit notifié.[/font]



    [font=helvetica, arial, sans-serif]Les deux solutions se valent, il n'y en a pas forcément une meilleure que l'autre (j'ai juste parlé des delegate pour t'indiquer l'existence de cette alternative), après cela dépend de ton besoin, du contexte et de tes préférences d'architecture.[/font]
  • Merci pour vos réponses, j'avais pensé à  faire un delegate, mais ça me semblait plus long à  mettre en place dans mon cas, et je voulais aussi jouer avec les NSNotifications, ça me rappelle beaucoup les signaux de QT en c++.

    Sinon, KIXX qu'entends tu par protocole?
  • Ce qu'AliGator a expliqué ^^.
  • AliGatorAliGator Membre, Modérateur
    février 2012 modifié #8
    En fait si tu n'as qu'un event ou deux à  signaler, et le vois plutôt comme "je signale à  tout le monde que j'ai fini, avis aux intéressés" et que tu laisses les intéressés s'abonner à  cette notification, les NSNotification sont en effet appropriées.



    Ce n'est pas tout à  fait le même mécanisme que les signaux/slots en QT, car en QT tu connectes in signal d'un objet à  un slot d'un autre objet, comme si tu avais une ficelle entre l'objet qui émet le signal et le slot qui le reçoit. Et pour ça tu as besoin à  un moment donné dans le code d'avoir les deux objets, pour pouvoir écrire [tt]connect(emetteur, SIGNAL(signal), recepteur, SLOT(slot));[/tt]. En ce sens les signaux/slots de QT ressemblent plutôt au mécanisme target/action de Cocoa plutôt.



    Alors qu'avec les NSNotification, l'émetteur de la notif et celui ou ceux qui s'y abonnent peuvent ne jamais se voir, et l'émission de la notif peut se faire dans un bout de code totalement séparé de l'abonnement de la notif pour indiquer que tu es intéressé (et bien sûr lui aussi séparé de la réception de la notif quand elle est émise). Tout ça passe par l'intermiédiaire qui est le NSNotificationCenter, ce qui fait que les deux parties peuvent ne jamais être présentes en mm temps dans un même bout de code, chacun vit sa vie séparément.



    ----



    Là  où le Design Pattern "delegate" commence à  devenir plus intéressant, c'est plutôt si un objet donné a besoin d'être informé du comportement d'un autre à  différents stades de sa vie, pour différents événements.



    Un exemple typique est une NSURLConnection pour télécharger des données. l'objet NSURLConnection informe son delegate qu'il a trouvé le serveur, démarre la récupération de la réponse, puis qu'il reçoit des morceaux de réponse bout par bout, puis qu'il a fini et a tout reçu... ou bien qu'il y a eu un problème ou une déconnexion, etc... Pour cela, il n'informe qu'un unique objet, à  travers le mécanisme de delegate.

    Ca ne serait pas très utile qu'il crie cette information sur tous les toits via des NSNotifications alors qu'en général seul l'objet qui se charge de gérer le téléchargement et de manipuler les données reçues est intéressé. Et en plus cela nécessiterai de traiter pas mal de notifications, vu qu'il y a pas mal d'événements différents.



    En plus l'avantage d'utiliser un @protocol c'est que les méthodes sont bien définies, avec des noms et des paramètres nommés. Alors qu'une NSNotification passe par un NSDictionary "userInfo" dont il faut connaà®tre les clés. C'est moins "parlant" à  la lecture du code.



    ----



    Autre cas où ce Design Pattern est pratique, c'est quand il est nécessaire d'obtenir une réponse. Par exemple si Fiston devait demander à  son Papa l'autorisation de faire qqch. Là  la NSNotification ne convient pas, car c'est un envoi de message que tu cries sur tout les toits à  tout le monde, sans pouvoir recevoir de réponse. Alors que dans le cas du pattern delegate tu peux créer un @protocol dont certaines méthodes vont retourner une valeur et pas "void", et te servir de cette valeur ensuite pour prendre des décisions.


    @implementation Fiston<br />
    -(void)playFootball<br />
    {<br />
      static const NSString* gameName = @&quot;football&quot;;<br />
      // demander la permission au paternel avant d&#39;aller jouer<br />
      BOOL canPlay = (self.delegate == nil) || [delegate fiston:self canPlayGame:gameName];<br />
      if (canPlay)<br />
      {<br />
    	NSLog(@&quot;Ouaiiis super les amis je vais jouer au foot avec vous&quot;);<br />
    	// Signaler au paternel qu&#39;on est parti<br />
    	[self.delegate fiston:self willPlayGame:gameName];<br />
      } else {<br />
    	NSLog(@&quot;Désolé les gars, mon vieux veut pas&quot;);<br />
      }<br />
    }
    
    Chose que tu ne peux pas faire avec une NSNotification évidemment que de demander l'avis du paternel image/wink.png' class='bbc_emoticon' alt=';)' />
  • ettiboettibo Membre
    février 2012 modifié #9
    Salut à  tous,

    avec le system d'event, mon application crash, quand je fais
    <br />
    [[NSNotificationCenter defaultCenter] postNotificationName:@&quot;DidFinishDownload&quot; object:nil];<br />
    




    j'ai ce message d'erreur:



    -[lefils receiveEvent:]: unrecognized selector sent to instance 0x687c9b0

    2012-02-06 17:49:29.128 MediActu[2099:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[lefils receiveEvent:]: unrecognized selector sent to instance 0x687c9b0'



    Des idees???
  • CéroceCéroce Membre, Modérateur
    février 2012 modifié #10
    Il te dit que la classe lefils ne répond pas à  la méthode d'instance receiveEvent:.
  • AliGatorAliGator Membre, Modérateur
    En gros la notification en elle-même est bien postée, et comme prévu tous ceux qui se sont abonnés à  cette NSNotification vont se voir notifier.

    Sauf que parmi ceux qui se sont abonnés à  cette Notification, tu en as un à  qui tu as dit en gros "si tu reçois cette NSNotification, appelle la méthode "receiveEvent:" sur tel objet". Or l'objet en question n'a pas de méthode "receiveEvent:" manifestement, puisque ça plante.



    Cas d'école : tu as peut-être une méthode "receiveEvent", sans paramètre, alors qu'il faut implémenter "receiveEvent:" avec les deux-points à  la fin donc avec un paramètre. Ce paramètre de cette méthode sera un objet de type NSNotification, contenant la NSNotification envoyée, dont tu pourras récupérer des informations (son nom, son dictionnaire userInfo, ...)
  • Donc, si je comprends bien même si c'est le père qui veut s'abonner et pas le fils, je dois aussi abonner le fils car c'est l'émetteur de la notification?
  • CéroceCéroce Membre, Modérateur
    Non. Celui qui s'abonne, c'est celui qui écoute.
  • Bah, c'est ça le truc, c'est que le père écoute, mais c'est le fils qui sort l'erreur
  • AliGatorAliGator Membre, Modérateur
    février 2012 modifié #15
    Heu non non non pas besoin d'abonner le fils



    Si c'est le père qui veut s'abonner à  la notification que le fils va émettre, pas de soucis.

    Il suffit d'écrire
    [[NSNotificationCenter defaultCenter] addObserver:father selector:@selector(childDidFinish:) name:kChildDidFinishNotificationName object:nil];
    
    pour dire que le père (father) est observeur de la notification nommée kChildDidFinishNotificationName et donc que quand cette notification est émise (par n'importe qui, il s'en fout le père que ce soit le fils qui émette la notif ou un autre), alors appeler la méthode "childDidFinish:" sur l'objet "father" pour le prévenir et traiter la notification.



    Encore faut-il que la classe Father implémente dans ce cas la méthode [tt]-(void)childDidFinish:(NSNotification*)notif[/tt] !! Car si ce n'est pas le cas, quand le fils va émettre la notification "kChildDidFinishNotificationName", le NSNotificationCenter va recevoir cette notif et la dispatcher à  tous les observeurs intéressés par cette notification (tous les objets abonnés) en appelant le sélecteur associé, donc dans notre cas il va appeler la méthode "childDidFinish:" sur l'objet "father", et si cette méthode n'existe pas, appeler une méthode qui n'existe pas sur un objet provoque l'exception que tu as eue.



    Je te conseille la lecture du Notification[url="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html#//apple_ref/doc/uid/10000043i]Notification"] Programming Guide[/url] pour plus d'infos, comme pour tous les Programming Guides d'Apple il est très complet et explique tous les principes en détail, avec exemples à  la clé.
  • AliGatorAliGator Membre, Modérateur
    'ettibo' a écrit:


    Bah, c'est ça le truc, c'est que le père écoute, mais c'est le fils qui sort l'erreur
    Ah oui j'avais pas vu ce détail dans l'exception que tu sors !



    Quel code utilises-tu pour abonner le père à  la notification ? Tu peux nous mettre ici les extraits de code que tu as écrit tant pour le père que pour le fils ?
  • ettiboettibo Membre
    février 2012 modifié #17
    Alors, pas de soucis, voilà  le code:
    <br />
    [[NSNotificationCenter defaultCenter] addObserver:self.lefils selector:@selector(receiveEvent:) name:@&quot;DidFinishDownload&quot; object:nil];<br />
    


    et en relisant ton code, je crois que je viens de comprendre, il faut que je change

    addObserver:self.lefils par addObserver:self



    et oui, c'était bien ça.

    Merci
  • AliGatorAliGator Membre, Modérateur
    Ah ben oui, le paramètre que tu passes à  "addObserver", c'est l'objet qui veut observer la notification et s'y abonner (donc le père), pas l'objet que tu veux observer et qui va émettre la notif (donc le fils).



    D'ailleurs, pour rappel, le père n'observe pas le fils ! Le père n'a pas à  avoir connaissance du fils.

    - Le père observe une notification @DidFinishDownload c'est tout. quel que soit celui qui va émettre cette notification.

    - Le fils va émettre la notification et la crier sur tous les toits (enfin va le crier au NotificationCenter en fait.

    - Mais le père et le fils ne sont pas liés directement, dans ce process. C'est même le principe du pattern utilisé dans les Notifications, l'un crie haut et fort un message à  qui veut l'entendre, et ensuite ceux qui sont intéressés en font ce qu'ils veulent, celui qui a crié le message s'en fout de qui l'a entendu, ce ceux qui l'ont entendu se foutent de connaà®tre celui qui a crié.



    Le NSNotificationCenter qui sert d'intermédiaire permet de faire la séparation, de recevoir toutes les notification émises d'un côté, de les redistribuer à  qui est intéressés de l'autre, mais chaque partie de part et d'autre n'a pas à  se connaà®tre.
  • C'est pour ça que je ne comprenais pas au début pourquoi tu me disais que c'était différent des signaux en QT,

    tu as l'air de bien connaitre que ce soit le c++ dont QT et l'objective C à  la fois, comment fais tu cet exploit?
  • 'ettibo' a écrit:


    C'est pour ça que je ne comprenais pas au début pourquoi tu me disais que c'était différent des signaux en QT,

    tu as l'air de bien connaitre que ce soit le c++ dont QT et l'objective C à  la fois, comment fais tu cet exploit?


    ça s'appelle l'expérience...jeune Padawan...
  • AliGatorAliGator Membre, Modérateur
    Bah c'est l'expérience, j'ai maintenant 7 ans d'expérience professionnelle après 5 ans d'école d'ingé, et je suis aussi pas mal autodidacte (je n'ai pas fait de la prog que en tant que salarié mais aussi en tant qu'auto-entrepreneur et aujourd'hui en tant que fondateur de startup).

    Au final j'ai vu des langages et des frameworks, de C, C++, C#, Objective-C et leurs libs/fmk respectifs stdlib, .QT, NET, Cocoa... sans parler des langages de scripting (AS, javascript, python, PHP) et j'en passe, la liste est longue.

    Mais il n'y a pas grand chose d'exceptionnel là  dedans, ceux ici qui ont autant d'expérience que moi ont aussi vu pas mal de langages dans leur carrière ; le propre d'un bon ingénieur est de savoir apprendre de nouvelles technos, et passer d'un langage à  l'autre n'est pas un problème quand tu es habitué. Ce ne sont que des outils au final.
  • Une autre question, est-il possible de passer des paramètres à  la notfication, comme un NSInteger?
  • AliGatorAliGator Membre, Modérateur
    février 2012 modifié #23
    Oui, via le userInfo.



    Mais encore une fois, va lire le Programming Guide associé (en plus bien sûr de la Class Reference) tout y est expliqué image/wink.png' class='bbc_emoticon' alt=';)' />

    Et tu as bien sûr aussi la "DevPedia" Apple qui t'explique le principe général également
  • Je pensais faire un système tout simple comme ça:



    le sender:



    - (void)sendNotif{



    NSNotification *tmp = [NSNotification notificationWithName:@validateButton object:nil];

    [tmp setValue:myUsername.text forKey:@user];

    [tmp setValue:myPass.text forKey:@pass];

    [[NSNotificationCenter defaultCenter] postNotification:tmp];

    }



    le receiver:





    - (void)sendLoginInformations:(NSNotification *)notification

    {

    NSLog(@Username: %@, Pass: %@", [notification valueForKey:@user], [notification valueForKey:@pass]);

    }



    mais ça n'apprécie pas le setValue image/sad.png' class='bbc_emoticon' alt=':(' />
  • AliGatorAliGator Membre, Modérateur
    Bon je vois que tu n'as donc toujours pas lu la doc.
  • Ok, j'ai compris ce que tu voulais me dire, mais je trouve que ma solution n'était pas mal du tout^^
  • AliGatorAliGator Membre, Modérateur
    février 2012 modifié #27
    - (void)sendNotif{<br />
      NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: myUsername.text,@&quot;user&quot;,myPass.text,@&quot;pass&quot;,nil];<br />
      [[NSNotificationCenter defaultCenter] postNotificationWithName:@&quot;validateButton&quot; object:nil userInfo:userInfo];<br />
    }
    



    - (void)sendLoginInformations:(NSNotification *)notification<br />
    {<br />
      NSLog(@&quot;Username: %@, Pass: %@&quot;, [[notification userInfo] valueForKey:@&quot;user&quot;], [[notification userInfo] valueForKey:@&quot;pass&quot;]);<br />
    }
    




    Je vois pas ce qu'il y a de compliqué, en plus le code est plus simple que ton pseudo-code (moins de lignes)
  • Oui, oui, je suis passé par cette solution finalement image/smile.png' class='bbc_emoticon' alt=':)' />
Connectez-vous ou Inscrivez-vous pour répondre.