KVO/KVC

Bonjour tout le monde,



J'ai deux propriétés distinctes, j'aimerais que l'une soit mise à  jour par l'autre, mais pas l'inverse.



Je m'explique: j'ai des préférences d'application et des préférences de document. Quand l'utilisateur ouvre un nouveau document, les préférences de celui-ci sont réglées sur celles de l'application, mais il est libre de les changer par la suite. Mais si les préférences de l'application changent, celles du document doivent changer aussi -- ainsi que dans les futurs documents.



Les préférences de l'application vont persister via le Shared User Controller, celles du document seront enregistrées avec le document.



Bref. Ce qui je veux, c'est visiblement un Observer (les propriétés du document vont "observer" celles de l'application). Questions:

1. Si un textField contenant une préférence d'application est modifié par l'utilisateur, est-ce qu'un autre textField contenant une préférence de document va voir son contenu changer également? (les deux textFields sont liés par "value" à  une propriété différente)

2. L'observation n'a pas lieu dans le sens contraire?



Merci de vos éclaircissements bienvenus!
Mots clés:

Réponses

  • mpergandmpergand Membre
    mai 2012 modifié #2
    Dans ce cas, j'utiliserais les notifications:

    - lorsque qu'une pref de l'appli change, une notification est envoyée

    - à  l'ouverture, chaque document s'enregistre pour recevoir cette notif.
  • berfisberfis Membre
    Oui, donc la méthode addObserver:, c'est bien ça? Si je veux que la modification se produise de manière interne à  l'application, je fais



    [notifCenter addObserver:self selector:@monSelecteur name:@MaVariableAChangeobject:maProprieteApplication];

    ?
  • HerveHerve Membre
    C'est marrant, je viens de bien me casser les dents sur ce sujet...



    Bon, si c'est en interne dans ton appli, les NSNotification suffisent, sinon ce sont les NSDistributedNotifications.



    Les classes qui doivent réagir aux notifications implémentent un écouteur de type :
    <br />
    [[NSNotificationCenter defaultCenter] addObserver:self<br />
    											 selector:@selector(laVoidQuiVaReagir:)<br />
    												 name:@&quot;leNomDonneALaNotification&quot;<br />
    											   object:dOuVientLaNotificationInstanceDeClasseEnInterneNomDuBundleSinon];<br />
    


    En général, les écouteurs sont dans une méthode appelée lors de l'initialisation



    Ta void est de type :

    - (void) laVoidQuiVaReagir:(NSNotification) notification;



    La classe qui émet la notification le fait avec :
    <br />
    [[NSNotificationCenter defaultCenter] postNotificationName: @&quot;leNomDonneALaNotification&quot;<br />
    														    object:self];<br />
    




    On peut même y adjoindre un NSDictionnary... (enfin, dans les interApp, en interne je n'ai jamais eu besoin)



    Mon synthé Dazibao est truffé de notifications pour que les graphiques commandent l'audio. Cela remplace les vieux jacks... (c'est marrant, Google rend hommage à  Moog aujourd'hui...)
  • CéroceCéroce Membre, Modérateur
    mai 2012 modifié #5
    Je pense pour ma part qu'utiliser le KVO est plus indiqué, parce que bien plus simple. En effet, il suffira d'observer les propriétés du Shared Defaults Controller.



    Utiliser NSNotificationCenter est une possibilité, mais qui va émettre les notifications ? Tu ne pourras plus binder les préférences sur le Shared Defaults Controller, il te faudra un contrôleur intermédiaire pour pouvoir intercepter les changements de préférences et émettre la notification.



    Et puis, les notifications sont en général une mauvaise idée, parce que 1) elles sont globales et 2) elle sont identifiées par des chaà®nes. Si tu utilises un objet intermédiaire, autant utiliser le design pattern Délégation: les appels de méthodes seront ciblés vers un seul objet, et vérifiés à  la compilation.
  • HerveHerve Membre
    Ce système propre à  Cocoa qui associe des clefs à  des valeurs est d'une façon générale très fécond

    (NSCoding, NSDictionary, @property, etc.)

    Cette idée simple dépasse les possibilités de la simple assignation de mémoire de C d'une façon étonnante!
  • CéroceCéroce Membre, Modérateur
    mai 2012 modifié #7
    Ce n'est pas un système propre à  Cocoa. Les dictionnaires (même s'ils s'appellent parfois "tableaux associatifs" ou "tables de hachage") sont courants dans beaucoup de langages (Java, Python, Ruby, PHP, JavaScript, etc.).
  • berfisberfis Membre
    mai 2012 modifié #8
    Merci à  tous, je pense donc être sur la bonne voie. J'ai réussi à  obtenir une "relation à  sens unique" entre deux textfields. L'un "gouverne" l'autre, et la réciproque n'est pas vraie. Parfait jusqu'ici.



    Mais lorsque j'essaie d'observer mon USD Controller, que dalle. J'écris ceci:




    [color=#c10611][b]@property[/b][color=#000000] [/color][b]IBOutlet[/b][color=#000000] [/color][color=#66261e][b]NSWindow[/b][/color][color=#000000] *window;[/color][/color]<br />
    [color=#c10611][b]@property[/b][color=#000000] [/color][b]IBOutlet[/b][color=#000000] [/color][color=#66261e][b]NSTextField[/b][/color][color=#000000] *fromData;[/color][/color]<br />
    [color=#c10611][b]@property[/b][color=#000000] [/color][b]IBOutlet[/b][color=#000000] [/color][color=#66261e][b]NSTextField[/b][/color][color=#000000] *toData;[/color][/color]<br />
    [color=#66261e][color=#c10611][b]@property[/b][/color][color=#000000] [/color][color=#c10611][b]IBOutlet[/b][/color][color=#000000] [/color][b]NSUserDefaultsController[/b][color=#000000] *udc;[/color][/color]<br />
    ...<br />
    - ([color=#c10611][b]void[/b][/color])applicationDidFinishLaunching:([color=#66261e][b]NSNotification[/b][/color] *)aNotification{<br />
    	[color=#c10611][b]unsigned[/b][/color] myDataHasChanged =[color=#2c9dbb][b]999[/b][/color];<br />
    [color=#8e372e][color=#000000]	[[/color][color=#548187][b]toData[/b][/color][color=#000000] [/color][b][i]setStringValue[/i][/b][color=#000000]:[[/color][color=#548187][b]udc[/b][/color][color=#000000] [/color][b][i]valueForKeyPath[/i][/b][color=#000000]:[/color][color=#2d9748][b]@&quot;values.from&quot;[/b][/color][color=#000000]]];[/color][/color]<br />
    [color=#8e372e][color=#000000]	[[/color][color=#548187]toData[/color][color=#000000] [/color]addObserver[color=#000000]: [/color][color=#548187]udc[/color][color=#000000] [/color]forKeyPath[color=#000000]:[/color][color=#2d9748][b]@&quot;values.from&quot;[/b][/color][color=#000000] [/color]options[color=#000000]:myDataHasChanged [/color]context[color=#000000]:[/color][color=#c10611][b]nil[/b][/color][color=#000000]]; }[/color][/color]<br />
    <br />
    - ([color=#c10611][b]void[/b][/color])didChangeValueForKey:([color=#66261e]NSString[/color]*)aString{<br />
    	[color=#8e372e]NSLog[/color]([color=#2d9748][b]@&quot;%@&quot;[/b][/color],aString);<br />
    [color=#8e372e][color=#000000]	[[/color][color=#548187]toData[/color][color=#000000] [/color]setStringValue[color=#000000]:[[/color][color=#548187]udc[/color][color=#000000] [/color]valueForKeyPath[color=#000000]:[/color][color=#2d9748][b]@&quot;values.from&quot;[/b][/color][color=#000000]]];}[/color][/color]
    


    J'obtiens cela:



    2012-05-25 15:49:17.694 null[12864:403] [<NSTextField 0x7fec3b10a1e0> addObserver:<NSUserDefaultsController 0x7fec3b111ca0> forKeyPath:@values.from options:0x3e7 context:0x0] was sent to an object that is not KVC-compliant for the "values" property.



    ... et donc mon "Observer"... n'observe rien.



    Où ai-je fauté?
  • CéroceCéroce Membre, Modérateur
    mai 2012 modifié #9
    Déjà , utilise les balises Code parce que là , c'est illisible.



    Deuxièmement, je te confirme que NSTextField n'a pas de propriété "values".
  • Nouvel essai.
    <br />
    [color=#c10611][b]@implementation[/b][/color] NullAppDelegate<br />
    [color=#c10611]<br />
    [b]@synthesize[/b][color=#000000] window;[/color][/color][color=#c10611]<br />
    [b]@synthesize[/b][color=#000000] toData;[/color][/color][color=#c10611]<br />
    [b]@synthesize[/b][color=#000000] udc;[/color][/color]<br />
    <br />
    - ([color=#c10611][b]void[/b][/color])applicationDidFinishLaunching:([color=#66261e][b]NSNotification[/b][/color] *)aNotification{[color=#8e372e]<br />
    [color=#000000]    [/color][color=#548187][b]udc[/b][/color][color=#000000] = [[/color][color=#66261e]NSUserDefaultsController[/color][color=#000000] [/color]sharedUserDefaultsController[color=#000000]];[/color][/color][color=#8e372e]<br />
    [color=#000000]    [[/color][color=#548187][b]toData[/b][/color][color=#000000] [/color][b][i]setStringValue[/i][/b][color=#000000]:[[[/color][color=#548187][b]udc[/b][/color][color=#000000] [/color][b][i]values[/i][/b][color=#000000]] [/color][b][i]valueForKeyPath[/i][/b][color=#000000]:[/color][color=#2d9748][b]@&quot;from&quot;[/b][/color][color=#000000]]];[/color][/color][color=#8e372e]<br />
    [color=#000000]    [[/color][color=#548187]udc[/color][color=#000000] [/color]addObserver[color=#000000]:[/color][color=#c10611][b]self[/b][/color][color=#000000] [/color]forKeyPath[color=#000000]:[/color][color=#2d9748][b]@&quot;values.from&quot;[/b][/color][color=#000000] [/color]options[color=#000000]: [/color][color=#2c9dbb][b]0[/b][/color][color=#000000] [/color]context[color=#000000]:[/color][color=#2d9748][b]@&quot;CHANGE&quot;[/b][/color][color=#000000]];[/color][/color]<br />
    }<br />
    - ([color=#c10611][b]void[/b][/color])didChangeValueForKey:([color=#66261e]NSString[/color]*)aString{[color=#2d9748]<br />
    [color=#000000]    [/color][color=#8e372e]NSLog[/color][color=#000000]([/color][b]@&quot;didChangeValueForKey %@&quot;[/b][color=#000000],aString);[/color][/color][color=#8e372e]<br />
    [color=#000000]    [[/color][color=#548187]toData[/color][color=#000000] [/color]setStringValue[color=#000000]:[[[/color][color=#548187]udc[/color][color=#000000] [/color]values[color=#000000]] [/color]valueForKeyPath[color=#000000]:[/color][color=#2d9748][b]@&quot;from&quot;[/b][/color][color=#000000]]];}[/color][/color]<br />
    [color=#c10611]<br />
    [b]@end[/b][/color]<br />
    <br />
    


    Là , la console commence à  déborder. Donc l'idée c'était, grâce à  "ce mécanisme merveilleux et si simple" que le text field "toData" se mette à  jour quand la valeur "from" du contrôleur était modifiée dans un autre text field.



    Où est (où sont) la (les) bourde(s)?
  • Bon, j'ai eu une partie de la solution sur un autre forum.





    1. D'abord la méthode pour répondre aux notifications n'est pas didChangeValueForKey mais celle-ci...


    <br />
    - ([color=#c10611][b]void[/b][/color])observeValueForKeyPath:([color=#66261e]NSString[/color] *)keyPath ofObject:([color=#c10611][b]id[/b][/color])object change:([color=#66261e]NSDictionary[/color] *)change context:([color=#c10611][b]void[/b][/color] *)context{<br />
    [color=#000000]    [/color][color=#c10611][b]if[/b][/color][color=#000000] ([keyPath [/color]isEqualTo[color=#000000]:[/color][color=#2d9748][b]@&quot;values.from&quot;[/b][/color][color=#000000]]){[/color][color=#c10611][b]if[/b][/color][color=#000000]([[/color][color=#548187]udc[/color][color=#000000] [/color]valueForKeyPath[color=#000000]:keyPath])[[/color][color=#548187]fromData[/color][color=#000000] [/color]setStringValue[color=#000000]:[[/color][color=#548187]udc[/color][color=#000000] [/color]valueForKeyPath[color=#000000]:keyPath]];}[/color][color=#8e372e]<br />
    [color=#000000]    [/color][color=#c10611][b]if[/b][/color][color=#000000] ([keyPath [/color]isEqualTo[color=#000000]:[/color][color=#2d9748][b]@&quot;values.to&quot;[/b][/color][color=#000000]]){[/color][color=#c10611][b]if[/b][/color][color=#000000]([[/color][color=#548187]udc[/color][color=#000000] [/color]valueForKeyPath[color=#000000]:keyPath])[[/color][color=#548187]toData[/color][color=#000000] [/color]setStringValue[color=#000000]:[[/color][color=#548187]udc[/color][color=#000000] [/color]valueForKeyPath[color=#000000]:keyPath]];}[/color][/color]<br />
    <br />
    }<br />
    <br />
    




    2. J'observe une propriété du sharedUserDefaultsController. C'est un contrôleur de dictionnaire. Imaginons que l'utilisateur raye d'un trait de "backspace" le contenu du champ observé. Que devient le key path? Comme il n'est plus lié à  rien, le contrôleur supprime la clé. Ensuite j'essaie de mettre le champ observateur à  jour. Et qu'est-ce que je lui envoie dans son setStringValue? Un joli NIL bien dodu... KVO n'apprécie pas du tout, inonde la console et se met en grève.



    Voila pourquoi je dois tester if([udc valueForKeyPath:keyPath]) avant de mettre à  jour, sinon: ticket pour la quatrième dimension...



    Les bindings, j'ai trouvé ça génial dès le début (cà d en ApplescriptObjC il y a un an). C'est une approche plutôt IB. Là  je découvre KVO/KVC, c'est une approche codée (en l'occurrence bourrée de IF assez moches). Ca ouvre des horizons, mais ce n'est pas évident au début...



    En tous les cas, merci pour les coups de mains et les avis.
Connectez-vous ou Inscrivez-vous pour répondre.