les Associative References: une inovation qui mériterait qu'on s'y intéresse
ClicCool
Membre
Avec SnowLeopart un certain nombre de nouvelles possibilités font leur apparition.
Si on a pas mal parlé des blocs qui semblent le mériter amplement, je n'ai rien trouvé sur le web (francophone ou anglophone) sur les Associative References.
En gros, elles permettent d'associer un Objet à un autre (c'est "tout bête" hein ?).
Cela permet, par exemple, d'ajouter de pseudo variables d'instances à un objet d'une classe sans avoir à la sous classer.
La Doc d'Apple, Associative References est peu causante, brève et montre juste en exemple l'association d'une NSTring à un NSArray.
où
- object est l'objet auquel on va en associé un autre
- value est l'objet associé
- La Key doit être unique et est donnée par une indirection, Apple recommande de se baser sur une variable statique et donc dans ce cas on utilisera un:
- L'objc_AssociationPolicy ressemble un peu aux attributs de déclaration des properties avec comme valeur possibles de policy:
C'est puissant car, en effet, l'objet associé peut peut être atomic et recevoir un retain et recevra alors un release quand on rompra l'association etc...
(un nouveau casse tête pour les frilleux de la gestion mémoire :P)
ATTENTION, le compilateur n'ayant aucun moyen de contrôle du type d'objet renvoyé, il me semble qu'il vaut mieux typer explicitement la valeur de retour de cette fonction. (même si Apple ne le dit pas explicitement)
Mais Apple en décourage l'usage dans sa doc:
A l'usage tout ça fonctionne parfaitement et me semble mériter notre d'intérrêt.
- Ces association pourraient ainsi lever en partie les limitations des catégories, à savoir l'impossibilité d'ajouter une variable dans une catégorie.
- plus simple encore: Un objet associé n'est en rien modifié et se comporte pareillement en particuliers dans les tests de comparaisons et d'égalité. Ce qui peut simplifier considérablement la vie pour gérer des collections.
Exemple fonctionnel:
Ici, avec seulement 4 lignes de code, on peut utiliser la rapidité et l'unicité des élements d'un Set pour gérer une collection d'ID associées à la référence vers le controller assigné.
Dans cette appli core-data, le but est de s'assurer qu'une seule fenêtre puisse être ouverte pour chaque objet et de récupérer s'il y a lieu le controller de cette fenêtre.
Et vous ? Quel usage trouvez vous aux Associative References ?
[EDIT] Ajout de la précaution de typecast avec la fonction objc_getAssociatedObject
Si on a pas mal parlé des blocs qui semblent le mériter amplement, je n'ai rien trouvé sur le web (francophone ou anglophone) sur les Associative References.
En gros, elles permettent d'associer un Objet à un autre (c'est "tout bête" hein ?).
Cela permet, par exemple, d'ajouter de pseudo variables d'instances à un objet d'une classe sans avoir à la sous classer.
La Doc d'Apple, Associative References est peu causante, brève et montre juste en exemple l'association d'une NSTring à un NSArray.
- En détail l'association est faite par le runTime et il ne faut pas oublier de placer un:
#import <objc/runtime.h>
- L'association d'un objet à un autre se fait avec la fonction:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy);
où
- object est l'objet auquel on va en associé un autre
- value est l'objet associé
- La Key doit être unique et est donnée par une indirection, Apple recommande de se baser sur une variable statique et donc dans ce cas on utilisera un:
static char AssociativeKey;
- L'objc_AssociationPolicy ressemble un peu aux attributs de déclaration des properties avec comme valeur possibles de policy:
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
C'est puissant car, en effet, l'objet associé peut peut être atomic et recevoir un retain et recevra alors un release quand on rompra l'association etc...
(un nouveau casse tête pour les frilleux de la gestion mémoire :P)
- L'objet associé peut être "récupéré" par:
id objc_getAssociatedObject(id object, void *key)
ATTENTION, le compilateur n'ayant aucun moyen de contrôle du type d'objet renvoyé, il me semble qu'il vaut mieux typer explicitement la valeur de retour de cette fonction. (même si Apple ne le dit pas explicitement)
- Une autre fonction existe pour supprimer toutes les associations à un objet:
void objc_removeAssociatedObjects(id object)
Mais Apple en décourage l'usage dans sa doc:
You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object.
- On peut donc aussi rompre une association donnée (correspondant à une clef donnée) en passant simplement nil comme value à la fonction de création d'une association:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy);
- On peut aussi changer l'objet associé, en se méfiant toutefois du release qui sera envoyé au précédent objet associé si on a pas opté pour OBJC_ASSOCIATION_ASSIGN.
- On peut aussi, par le biais de plusieurs clefs différentes, associer à un objet autant d'objets que l'on veut.
A l'usage tout ça fonctionne parfaitement et me semble mériter notre d'intérrêt.
- Ces association pourraient ainsi lever en partie les limitations des catégories, à savoir l'impossibilité d'ajouter une variable dans une catégorie.
- plus simple encore: Un objet associé n'est en rien modifié et se comporte pareillement en particuliers dans les tests de comparaisons et d'égalité. Ce qui peut simplifier considérablement la vie pour gérer des collections.
Exemple fonctionnel:
Ici, avec seulement 4 lignes de code, on peut utiliser la rapidité et l'unicité des élements d'un Set pour gérer une collection d'ID associées à la référence vers le controller assigné.
Dans cette appli core-data, le but est de s'assurer qu'une seule fenêtre puisse être ouverte pour chaque objet et de récupérer s'il y a lieu le controller de cette fenêtre.
<br />#import <objc/runtime.h><br />static char objectCTRLKey;<br />.../...<br />// Ici on veut "ouvrir" tous les object sélectionnés d'un arrayControlleur<br /> for (lobjet in selectedObjects ) {<br /> objetID = [lobjet objectID]; // On récupère l'ID de l'objet<br /> if (! objetIDinSet = [IDOuvertsSet member: objetID]) { // s'il n'est pas référencé dans le set<br /> [IDOuvertsSet addObject: objetID]; // on l'y place<br /> ObjectViewCTRL* objectCTRL = [[ObjectViewCTRL alloc] initWithObjectID: objectID]; // on lance l'ouverture de la fenêtre<br /> objc_setAssociatedObject(objectID, & objectCTRLKey, objectCTRL, OBJC_ASSOCIATION_ASSIGN); // on associe le controlleur à l'ID qui est dans le set<br /> } else { // L'objet est déjà en cours d'édition<br /> ObjectViewCTRL *objectCTRL = (ObjectViewCTRL *)objc_getAssociatedObject(objetIDinSet, &objectCTRLKey);<br /> // On fait ce qu'on veut avec le controleur d'édition de l'Objet <br /> }<br /> } <br />
Et vous ? Quel usage trouvez vous aux Associative References ?
[EDIT] Ajout de la précaution de typecast avec la fonction objc_getAssociatedObject
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je trouvait dommage que ça passe inaperçu parmis les nouveautés, pourtant pas si nombreuses, qu'apporte 10.6 8--)
Oups, j'oubliais un détail important.
Les association étant faites par le runTime, le compilo n'a aucun contrôle sur le type d'objet récupéré.
Il vaut mieux donc le "typCaster" en dur dans le code.
Comme dans l'exemple que j'ai donné:
Je me demande si je ne devrai pas éditer mon premier post pour y ajouter cette précision non explicité dans la doc d'Apple ??
J'le fais ? ? ???
Cela fait penser à une généralisation des key-value coding compliant container class comme CALayer et CAAnimation.
Je trouve que ça fait sauter pas mal de limites des catégories (ajout de variable à une instance mais aussi accès aux méthodes de la classe mère via super) en se rapprochant de la notion d'héritage multiple.
Est ce qu'on sait ce qui se passe lorsque deux classes implémentes des méthodes identiques et qu'on les associes?
@Louis XVI: Y'a des similitudes en effet dans le fait d'éviter d'ajouter des variables d'instances en sous-classant.
Par contre les Associative References ne sont pas KVO compliantes telles que.
Mais en s'en servant conjointement à une catégorie qui définirait des accesseurs KVO compliant donnant accès à l'objet associé ça devrait marcher et on pourrait alors utiliser la "dot Syntax" et même aussi s'amuser à binder sur l'objet associé ! :brule:
Strictement parlant on associe pas deux objets ensemble, on associe la référence vers un objet à un autre.
L'objet associé n'a aucune référence vers l'objet auquel il est associé.
Et, sauf si on implémente des méthodes spécifiques (par le biais d'une catégorie par exemple), les deux objets restent indépendants (c'est un aspect qui me plait bien justement) et n'ont pas accès aux iVars ni aux méthodes de l'autre.
J'ai pas eu le temps de tout lire et d'étudier la question par contre, mais quel différences/avantages/inconvénients ça a par rapport à une classe qui serait auto-KVC un peu comme les CALayers, sur laquelle on pourrait faire [tt]valueForKey:[/tt] et [tt]setValue:forKey:[/tt] pour n'importe quelle clé arbitraire même une qui ne correspond à aucune variable d'instance ?
cela rapproche encore plus Obj-C des langages interprétés.
je n'ai pas connaissance de langage compilé offrant déjà cette possibilité.
merci pour l'info
A+JYT
En fait, si j'ai bien compris les CALayers et autres key-value coding compliant container class, ce sont, sur cet aspect, des containers au même titre qu'un Dictionary.
Ici, comme je le répondais à Philippe XVI, rien à voir avec la notion de key-value coding compliant container.
Je conçois que ça puisse être utile, mais j'ai encore du mal à cerner dans quels cas.
Par exemple, pour le use-case que tu donnes (ton NSSet avec tes ID et tes Controllers associés), je ne vois pas l'intérêt dans le sens où moi j'aurais pas fait du tout comme ça : au lieu d'utiliser un NSSet, j'aurais simplement utilisé un NSDictionary, me servant de l'ID comme clés du dictionnaire et associant le contrôleur en valeur associée à cet ID.
Cette solution conserve l'avantage de l'unicité (les clés d'un NSDictionary sont uniques) et en plus est à mon avis plus performante (bon ça reste à vérifier mais c'est probable) puisque basé sur une table de hachage.
Code que, personnellement, je trouve plus compréhensible (mais bon c'est une affaire de goût ça aussi, j'admet). C'est aussi pour ça que je te faisais le parallèle avec les "KVC-compliant container classes"... Ou alors j'ai toujours pas tout compris... ;D
Après, au sens association pure, genre le cas du "overview" de l'exemple Apple, je comprend le sens que ça peut avoir que cette association, mais j'ai du mal à en trouver des use-cases pratiques pour l'instant...?
C'est ce que je faisait avant en effet.
Je reste persuadé que le set est plus performant néanmoins.
Plus encore, dans les cas où j'aurais besoin de trier mes ID en les mettant dans un Array, c'est la seule solution élégante.
Les clefs d'un dictionnaire sont uniques mais il n'est pas rapide de trier un dictionnary par ses clefs
Et enfin c'est plus évolutif, et quand l'application nécessitera que j'associe aussi un TimeStamp d'ouverture ou tout autre information, je n'aurais qu'à associer ces objets d'information à l'objetID qui restera toujours aussi facile à gérer dans son Set (ou son Array...).
[EDIT] P.S.: Les sets aussi sont basés sur une table de hashage
Enfin, bon, après, comme je le disais, ton use-case ne me semble pas des plus pertinents, en tout cas moi je ne vois pas un avantage si énorme à ces Associative References dans ce genre de cas là (d'autant que du coup je trouve moins clair de faire des appels à des méthodes bas niveau et dépendantes du Runtime quand on a une solution "plus Objective-C et objet / KVC" à disposition.
Maintenant, dans d'autres cas, comme simuler l'ajout de variables d'instances via une catégories d'une classe, pourquoi pas. Ca commence à être alors alambiqué comme architecture logicielle et façon de faire, mais bon, si jamais on n'a pas le choix... :P
Et ce thread n'a pas non plus vocation à faire l'éloge inconditionelle des Associative References, mais plus simplement d'attirer l'attention sur cette nouveauté que beaucoup semblaient ne pas même avoir remarquée.
Après, ce qui m'intéresse ici c'est le point de vue de chacun sur l'intérêt, les avantages et inconvénients de cette nouveauté et sur la place qu'elle pourrait être amenée à prendre dans nos projets.
P.S. ça me rapèle un peu le temps de l'apparition des Bindings que beaucoups ont dénigré pendant des années avant de leur accorder une place dans leur pratique.
P.P.S. j'avais édité le post auquel tu semble faire référence.
Bien dommage que le forum n'affiche pas la traditionnelle anotation donnant la date/heure de la dernière édition.
Pareil avec le CALayer, on peut lui donner un identifier par exemple.
Donc cette possibilité d'associer à n'importe quelle classe un objet me semble intéressante. Notamment pour les classes non sous-classables comme NSString. Jusqu'à maintenant, on faisait un wrapper, là on a une solution moins lourde. Je teste cela.
Effectivement, je suis d'accord avec toi Ali, une syntaxe "Cocoa" serait quand même plus sympathique. Qu'entends-tu par "méthodes dépendantes du Runtime" ?
Dans ce sens là , les associations semblent en effet intéressantes pour des cas similaires sur des classes ne supportant pas le "KVC-compliant container class" comme CALayer.
Sinon par "méthodes dépendantes du runtime" je me suis mal exprimé, je voulais pas dire dépendantes, juste "méthodes bas niveau du runtime". Bon elles sont dépendantes du runtime utilisé (Objective-C 1.0 ou 2.0), mais bon en pratique on s'en fiche un peu maintenant tout le monde utilise le dernier RunTime
Après, au même titre que j'ai fait une catégorie de NSObject pour ma méthode releasePropertiesForObject (cf mon Truc & Astuce dans l'autre partie du forum), on peut faire un méthode [tt]setAssociatedObject:forKey:[/tt] dans une catégorie de NSObject pour rendre ça plus user-friendly à coder. Car j'avoue que ce genre de syntaxe C avec ces noms de fonctions, je suis pas fan :P
ça c'est une idée qu'elle est bonne !
L'idée est d'associer ou non une qualité à un objet.
Ce qui n'est pas clair pour moi pour l'instant c'est la clé. Où va-t-on la mettre dans un projet ?
Dans l'unité de compilation où on fait l'association .. cela limite la portée de l'association ??
Les Associative References majorent encore un peu le coté dynamique de l'ObjC, et en même temps il me semble que ces clefs en limite la souplesse.
Ton exemple m'intéresse car de mon côté je partais plus sur des methodes d'instances de type Setter associant un objet quelquonque au receveur et Getter renvoyant l'objet lié au receveur.
Pour ton exemple tu est donc parti sur une méthode de classe bien propre qui renvoie un nouvel objet bien propre lié à un autre.
Mais comment sera exploitée cette référence après ?
La doc dit juste:
En gros, faut qu'elles soient uniques.
Le fait qu'elles soient déclarées en statique est juste une juggestion d'usage typique.
Je viens de faire un test rapide en balançant un pointeur sur une NSString créée chaques fois juste avant l'appel aux fonction avec un simple et ... ça a l'air de marcher correctement à première vue et j'associe et récupère les bons objets selon le contenu de la NSString !Â
Ce serait la porte ouverte à l'implémentation de methodes KVO genre setValue: (id) value forKey: (id) key etc ...
Je tenterais ça dès que possible
Oui, qu'elles soient uniques pour un objet donné.
Le fait d'autoriser le cas non static donne la possibilité d'avoir une visibilité de cette clé dans l'ensemble d'un projet. Bien.
En fait la clé ne semble servir que par son adresse, et on utilise le compilateur pour nous fournir des adresses sûrement différentes. Ok ?
Difficile de construire l'exemple à partir de la solution. L'idée de l'exemple est la possibilité d'adjoindre une sorte de type perso, une qualité à un objet. La qualité liée à countryKey pourrait d'ailleurs tout à fait s'associer à une NSString une NSMutableString, un NSNumber (deux qualités:c'est une population+c'est un pays), une collection(c'est un ensemble de villes du même pays), un NSData(ce data décrit les données d'un pays), une classe perso(un pays). Maintenant, cela ouvre des horizons, à voir si cela simplifie la lecture d'un code.
Problème 1 : Si tu des NSString non pas statiquesmais créées avec stringWithFormat... tu as beau passer la même clé au sens "même chaà®ne", ça ne marchera pas. Bon en même temps c'est compréhensible, mais c'est juste qu'il ne faut pas penser à la clé comme celles utilisées par exemple dans un NSDictionary (qui utilises isEqual pour leur comparaison, elles)
Problème 2 : Si tu utilises autre chose que des NSString à la limite c'est pareil : c'est pas utilisé avec isEqual pour trouver le bon objet associé à la bonne clé, mais bien une égalité pure
Problème 3 : Si tu passes tes objets entre diverses librairies ou divers frameworks de ton application, je ne suis pas sûr que les objets statiques passent "au travers" de ce passage inter-librairies. En particulier si tu associes un objet à ton objet dans ton appli, et que tu veux récupérer l'objet associé depuis une autre librairie...
Après, je me demande comment il se sert de ce void*. Etant donné que pour lui c'est vraiment un void* au sens où il ne sais vraiment pas ce que c'est, il ne va pas manipuler l'objet ou la mémoire que l'on lui passe, genre il ne va pas essayer d'appeler une fonction sur l'objet passer pour retrouver le associatedObject, ça me parait évident. Donc pour moi il utilise uniquement le pointeur (l'adresse de l'objet, pas sa valeur). Du coup je pense que rien ne nous empêche de passer un entier quelconque (casté en void* bien sûr)... Et là du coup plus simple de manipuler tout ça sans se poser de question de portée des variables statiques utilisées...
oui manifestement, les clés (countryKey dans l'exemple) ne sont pas traitées par isEqualToString mais par une simple comparaison d'adresse, le isEqual de base.
Tout à fait d'accord, simplement le fait de l'associer à une variable permet d'une part la clarté dans le code, assurer peut-être un traitement en arrière de l'ensemble de ces keys (le compilateur ne fait-il pas un tableau de ces keys ?), assurer que ces références de clés (=adresses) sont différentes.
Que veux tu dire par "pour un objet donné" ?
Si c'est qu'un objet donné ne peut avoir qu'un seul autre objet associé avec cette clef OK
Mais la même clef peut servir à d'autres objets pour qu'eux aussi aient un objet (unique) associé via cette clef.
C'est pour tester ça justement que je construisais chaque fois une nouvelle NSString à la volée juste avant l'appel des fonctions.
Mais il est vrai que ma façon d'initialiser ma NSString avait toutes les chances de renvoyer sur la même adresse.
J'ai donc testé avec 2 clefs différentes en associant 2 objets différents au même objet. Et j'ai initialisé mes clefs ainsi:
// Association ou récupération d'un Objet
NSMutableString * clef2 = [[NSMutableString alloc] initWithString:@clef];
[clef2 appendString:@2];
// Association ou récupération d'un autre Objet[/code]
Les clefs sont déclarées en locale et initialisées juste la ligne avant avant les fonction objc_setAssociatedObject et objc_getAssociatedObject.
L'association se passe bien et je récupère les bons objets selon la valeur de la NSString crée en clef.
Sur ce coup il me semble que l'adresse n'est pas la même chaque fois et que c'est l'identité de la mémoire pointée qui est testée non ?
Oui ça marche.
J'ai essayé hier avec plusieurs clefs déclarées ainsi:
sans problème
[EDIT] ce qui prouve rien tant que j'aurais pas ajouté un:
pour vérifier si c'est l'adresse ou le "contenu" ...
En tout cas j douterai vraiment fortement que ce soit le contenu qui soit utilisé, ça n'aurait pas de sens. C'est vraiment juste un "==" entre le void* qu'on passe dans "objc_getAssociatedObject" et la liste de toutes les autres clés void* dans lesquelles il cherche.
Pour moi ils auraient pu vraiment tout aussi bien utiliser un [tt]long long int[/tt] plutôt qu'un void* puisqu'en interne ils font qu'une comparaison sur l'adresse, donc juste un entier en qques sortes. Avoir mis un type "void*" c'est juste pour nous permettre de pouvoir passer n'importe quoi à la fonction (et nous inciter à passer un pointeur vers un objet ou autre) sans avoir à "caster". Enfin j'imagine mais tout semble indiquer que ça marche comme ça
Non elle n'est pas initialisée, c'est simplement l'adresse de cette clé qui est utilisée. Le contenu est complètement inutile.
Effectivement static int countryKey, ou int countryKey , ou struct {char name[10]; void * data;} countryKey={"trucmoche",NULL}; c'est pareil que static char countryKey;
Oui c'est cela que je voulais dire, l'unicité de la clé pour l'association avec un objet donné.