Accueil > Développement > Les notifications en Objective-C - Partie 2

Les notifications en Objective-C - Partie 2

Par Renaud Préat, le 06/06/2003

Dans le premier volet de cet article, nous vous avions présenté les principes généraux de l’usage des notifications. Dans cette deuxième partie, nous examinerons plus en profondeur le système de fonctionnement des notifications, et plus particulièrement les problèmes qui peuvent survenir lors de l’envoi de notifications à un objet qui n’est pas en état d’en recevoir, parce qu’il est occupé à exécuter une méthode par exemple. Ce cas peut se produire lorsque l’observateur est situé dans une autre tâche que le centre de notifications envoyeur. Ceci s’avère problématique dans la mesure où l’objet envoyeur attend une confirmation de réception pour exécuter la suite du code : il suffit donc d’un observateur qui ne soit pas en état de recevoir une notification pour paralyser une tâche. C’est dans ce contexte que nous aborderons les notifications asynchrones, qui n’ont pas cette restriction. Bien entendu, ce type de notification n’a un sens que dans le seul cas des notifications adressées au centre de notification distribué.

Files d’attente

Pour commencer, il est nécessaire d’introduire un nouvel objet : la file d’attente (un objet NSNotificationQueue), qui est un intermédiaire entre l’objet envoyeur et le centre de notification, lorsque le programmeur le spécifie. Je m’explique : lorsque vous utilisez les méthodes présentées dans la première partie pour l’envoi d’une notification, la file d’attente n’est pas utilisée. Pour pouvoir utiliser cet objet, il faut envoyer les notifications explicitement à la file d’attente, sans passer par le centre de notification. La file d’attente a deux rôles particuliers : elle trie les notifications, de façon à ce que les objets destinataires ne reçoivent pas deux fois la même notification, et permet d’envoyer des notifications asynchrones, c’est-à-dire des notifications dont l’envoi et la réception sont différés : la file d’attente étant capable d’envoyer les notifications au centre au moment où un objet devient disponible pour leur réception. Ces points sont détaillés dans les lignes qui suivent :

Envoi de notifications  via la file d’attente

Envoyer une notification via la file d’attente est plus complexe que de l’envoyer directement au centre, pour la bonne et simple raison qu’il n’y a pas de méthodes de commodité qui permettent un envoi direct.

La première chose à faire est de créer un objet NSNotification, en utilisant une méthode de classe notificationWithName:object: ou notificationWithName:object:userInfo:. Les différents arguments sont semblables à ce qui a été vu pour l’envoi de notification via les méthodes postNotificationWithName:object:(userInfo:) du centre de notification, nous n’y reviendront donc pas.

Une fois la notification créée, on peut l’envoyer à la file d’attente. La file d’attente est un objet de type NSNotificationQueue. Pour y accéder, on peut soit utiliser la méthode de classe defaultQueue qui renvoie la file d’attente pour le centre de notification de la tâche. S’il s’agit d’un autre centre, il faut utiliser la méthode d’instance initWithNotificationCenter: avec comme seul argument le centre de notification auquel doit être attaché la file d’attente.

Maintenant qu’on a la file d’attente et la notification, on peut envoyer la notification, grâce la méthode enqueueNotification:postingStyle:coalesceMask:forModes:. Le premier argument est la notification, le second correspond au moment où la notification sera envoyée au centre, le troisième argument correspond aux critères de tri, utilisés pour fusionner les notifications redondantes et le quatrième correspond à l’état dans lequel doit se trouver la tâche qui contient l’objet destinataire. Ces trois derniers arguments sont détaillés dans les points qui suivent.

Envoi différé (postingStyle)

L’argument postingStyle permet de définir le moment où la notification doit quitter la file d’attente et aller au centre, et ce en fonction de la disponibilité de l’objet envoyeur. On distingue trois types d’envois :

Argument Effet
NSPostASAP la notification est envoyée lorsque le code en cours d’exécution dans la boucle est entièrement exécuté (y compris les éventuels NSTimers et autres notifications asynchrones).
NSPostWhenIdle la notification est envoyée une fois que la boucle est en état d’attente, c’est-à-dire que le code a été totalement exécuté, à l’exception des NSTimers et d’autres notifications asynchrones.
NSPostNow la notification est envoyée immédiatement.

NSPostASAP sera utilisé plus particulièrement pour les processus très gourmands en ressources, NSPostWhenIdle sera utilisé par exemple en réponse aux actions d’un utilisateur.

Fusion des notifications (coalesceMask)

Pour éviter que les objets enregistrés reçoivent plusieurs fois la même notification, il est possible pour la file de fusionner toutes les notifications redondantes en une seule, de façon que le centre ne reçoive qu’une seule notification, et donc que le code correspondant à l’arrivée de la notification chez les objets enregistrés ne soit exécuté qu’une seule fois.

Dire que les notifications doivent être fusionnées est bien, mais sur quel critère se baser pour pouvoir dire que des notifications sont identiques ? La réponse est le classique « Ça dépend» : parfois la fusion devra s’effectuer en fonction du nom (par exemple, dès qu’un champ texte change, mais peu importe lequel), parfois en fonction de l’objet (par exemple dès qu’un changement est effectué dans une table définie, quel que soit le type de changement), ou éventuellement les deux. Le rôle de cet argument est précisément de définir ces critères.

Pour paramétrer la façon dont les notifications devront être fusionnées, il vous faudra placer les arguments repris dans le tableau suivant :

Argument Critère

NSNotificationNoCoalescing

Pas de fusion

NSNotificationCoalescingOnName

Les notifications portant le même nom sont fusionnées.

NSNotificationCoalescingOnSender

Les notifications comportant le même objet sont fusionnées.

Il est possible de combiner deux modes de fusion grâce à l’opérateur | (pipe).

Modes d’exécution de la tâche (forModes)

Pour terminer, le dernier argument sert différer l’envoi de la notification suivant un dernier critère, l’état de la boucle d’exécution qui contient l’objet envoyeur.

Il existe quatre modes d’exécution pour une boucle :

  • NSDefaultRunLoopMode : mode par défaut, la boucle attend des événements autres que la réponse d’un objet NSConnection ;
  • NSConnectionReplyMode : la boucle attend une réponse d’un objet NSConnection (très rarement utilisé) ;
  • NSModalPanelRunLoopMode (NSApplication) : la boucle attend des informations en provenance d’un panneau modal ;
  • NSEventTrackingRunLoopMode (NSApplication) : en attente de la fin d’événements modaux (comme une opération de glisser-déposer).

Il est nécessaire de créer un tableau contenant les différents modes pour lesquels il faut envoyer les notifications, et de placer ce tableau comme argument. Si on attribue nil à cet argument, tous les modes sont acceptés.

Pour faire plus simple…

Il existe une méthode de commodité permettant de ne pas avoir à remplir tous ces arguments, qui est enqueueNotification:postingStyle:. Cette méthode effectue une fusion en fonction de l’objet et du nom de la notification, et envoie la notification quel que soit le mode d’exécution de la tâche.

Centre de notification distribué

Il est également possible de paramétrer le centre de notification distribué d’une tâche pour qu’il cesse l’envoi de notifications si certaines conditions (ne) sont (pas) remplies (par exemple, il faut que le programme soit actif). Dans le cas des applications écrites avec l’Application Kit, la suspension de l’envoi de notifications se fait automatiquement, la classe NSApplication bloquant l’envoi de notifications « distribuées » lorsque l’application n’est pas active. Pour certains programmes écrits avec Foundation uniquement, il peut être utile de pouvoir paramétrer cela, et ce sera l’objet de ce point.

La suspension se fait par l’envoi du message setSuspended: au centre de notification, avec comme argument YES pour suspendre l’envoi de notifications, et NO pour permettre l’envoi. Là-dessus, une question peut se poser : que deviennent les notifications envoyées au centre lorsque celui-ci est suspendu ? Eh bien …ça dépend. Par défaut, le centre garde les notifications en mémoire, en les fusionnant sur base du nom et de l’objet (toujours de type NSString pour les notifications envoyées via le centre de notification distribué), puis lorsque qu’on réactive le centre de notification, il les envoie. Cette situation peut cependant ne pas convenir à tous les cas. Il est donc possible lors de paramétrer le centre de notification pour que l’envoi des notifications puisse se faire de différentes manières :

  • NSNotificationSuspensionBehaviorDrop : toute notification qui arrive au centre est supprimée lorsque ce dernier est dans l’état suspendu ;
  • NSNotificationSuspensionBehaviorCoalesce : les notifications sont fusionnées jusqu’à ce que le centre soit réactivé, et elles sont alors envoyées (c’est le comportement par défaut) ;
  • NSNotificationSuspensionBehaviorHold : toutes les notifications sont placées dans une file, et lorsque celle-ci est remplie, elles sont envoyées (la taille de la file est déterminée par le serveur) ;
  • NSNotificationSuspensionBehaviorDeliverImmediately : la notification est envoyée que le centre soit suspendu ou non.

Le comportement n’est pas en fait pas défini pour le centre de notification, mais pour un observateur, ce rend possible différents comportements pour un même centre. La méthode à utiliser pour déclarer un observateur est : addObserver:selector:name:object:suspensionBehavior:. Les arguments sont du même type que ce qui a été vu dans l’article précédent. Pour suspensionBehavior:, utiliser les noms des comportements donnés dans la liste ci-dessus. Il est un point qu’il faut remarquer, lors de l’usage des comportements NSNotificationSuspensionBehaviorHold et NSNotificationSuspensionBehaviorDeliverImmediately, l’ordinateur détermine lui-même lorsque l’envoi de notifications doit se faire, et pour envoyer une notification, il activera le centre de notification (avec un message setSuspended:YES). Les notifications conservées dans une file avec le comportement NSNotificationSuspensionBehaviorCoalesce seront donc aussi envoyées à ce moment.

Il peut arriver que certaines notifications doivent être traitées immédiatement, quel que soit le mode de suspension du centre, comme une notification informant l’arrêt de l’ordinateur, ou la mise en veille. Ce cas est également prévu : lors de l’envoi de la notification, il existe une méthode qui permet l’envoi immédiat : postNotificationName:object:userInfo:deliverImmediately:, avec YES comme valeur pour le dernier argument. Cependant, la réception et le « traitement » de la notification n’est pas garanti. Si une tâche est trop occupée pour traiter une notification, cette dernière sera ignorée.

Résumé

NSNotificationQueue

Cet objet permet d’envoyer de fusionner des notifications avant leur envoi au centre, et d’en différer l’envoi.

Pour avoir la file d’attente, il faut soit utiliser la méthode de classe +defaultQueue pour la file d’attente rattachée à la tâche, ou la méthode de classe initWithNotificationCenter: pour la file d’attente rattachée au centre de notification placé comme argument.

Pour envoyer une notification à la file d’attente, les méthodes suivantes existent :

  • enqueueNotification:postingStyle:coalesceMask:forModes: avec comme arguments :
    • un objet NSNotification ;
    • un style d’envoi : qui peut prendre les valeurs NSPostNow, NSPostASAP (pour les notifications dont le traitement est lourd en terme de ressources) et NSPostWhenIdle (pour les notifications dont le traitement n’est pas trop exigeant);
    • un mode de fusion : qui peut prendre les valeurs NSNotificationNoCoalescing, NSNotificationCoalescingOnName, NSNotificationCoalescingOnSender (éventuellement combinés avec un pipe (|));
    • le mode d’exécution de la boucle : est un NSArray qui peut contenir les valeurs suivantes : NSDefaultRunLoopMode, NSModalPanelRunLoopMode, NSEventTrackingRunLoopMode, NSEventTrackingRunLoopMode.
  • - enqueueNotification:postingStyle: avec comme arguments les deux premiers de ma méthode précédente.

NSDistributedNotificationCenter

Il est possible de suspendre l’envoi de notifications pour le centre de notifications distribué. Dans le cas des programmes écrits avec l’Application Kit, la suspension est automatique, dans le cas des programmes écrits avec Foundation, la suspension (ou la réactivation) se fait avec la méthode setSuspended:.

Lors de la création d’un observateur, il est possible de définir la façon dont les notifications devront être traitées pour cet observateur. On définit ce comportement lors de l’enregistrement de l’observateur par la méthode addObserver:selector:name:object:suspensionBehavior:. Les quatre premiers arguments ont été décrits dans le premier article, le dernier prends les valeurs NSNotificationSuspensionBehaviorDrop (ignorer les notifications envoyées pendant que le centre est suspendu), NSNotificationSuspensionBehaviorCoalesce (fusion des notifications redondantes et envoi lors de la réactivation) NSNotificationSuspensionBehaviorHold (conservations des notifications dans une file, et envoi lorsque cette file est pleine), NSNotificationSuspensionBehaviorDeliverImmediately (envoi immédiat).

Si une notification est envoyée pour signaler quelque chose d’extrêmement important, on peut passer outre le comportement défini par le centre, en utilisant la méthode postNotificationName:object:userInfo:deliverImmediately: pour l’envoi de la notification, avec YES comme valeur pour le dernier argument.

Conclusion

Avec ces deux articles, les notifications ont été totalement expliquées (d’un point de vue théorique, je vous l’accorderai volontiers), il ne vous reste plus qu’à mettre en pratique ce formidable outil.
Bon code !

© Septembre 2003 Renaud pour Project:Omega

renaud Développement ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire