Saletés de @property(retain) à releaser !
AliGator
Membre, Modérateur
Hello,
Comme j'en ai marre dans mes programmes, où j'utilise massivement des @property(retain), de devoir releaser mes variables dans le dealloc, je me suis attaqué au problème.
J'ai donc fait une catégorie de NSObject pour lui rajouter une méthode, qui va parcourir toutes les @property(retain) ou @property(copy) et releaser tout seul les variables associées.
Dites-moi ce que vous en pensez, je suis preneur de commentaires sur le code, et j'ai p'tet oublié des cas alambiqués...
Exemple d'utilisation :
Notes :
Comme j'en ai marre dans mes programmes, où j'utilise massivement des @property(retain), de devoir releaser mes variables dans le dealloc, je me suis attaqué au problème.
J'ai donc fait une catégorie de NSObject pour lui rajouter une méthode, qui va parcourir toutes les @property(retain) ou @property(copy) et releaser tout seul les variables associées.
Dites-moi ce que vous en pensez, je suis preneur de commentaires sur le code, et j'ai p'tet oublié des cas alambiqués...
#import <Foundation/Foundation.h><br /><br />@interface NSObject(AutoDealloc)<br />+(void)releasePropertiesOfObject:(id)object;<br />@end
#import "NSObject+AutoDealloc.h"<br />#import <objc/runtime.h><br /><br />@implementation NSObject(AutoDealloc)<br />+(void)releasePropertiesOfObject:(id)object<br />{<br /> // Warning, 'self' here is a Class!<br /> NSAssert([object isKindOfClass:self],@"releasePropertiesOfObject: inconsistent object passed as argument");<br /> <br /> unsigned int propertiesCount;<br /> objc_property_t* properties = class_copyPropertyList(self, &propertiesCount);<br /> for (unsigned int i=0; i < propertiesCount; i++)<br /> {<br /> objc_property_t property = properties[i];<br /> const char* attr = property_getAttributes(property);<br /> if ( (strstr(attr,",&,")!=NULL) || (strstr(attr,",C,")!=NULL) ) // (retain) || (copy)<br /> {<br /> // this prop is (retain) or (copy) so find the backing store variable<br /> const char* backingVar = strstr(attr,",V");<br /> if (backingVar) // if we have a variable attached to this property, release it<br /> {<br /> const char* varName = backingVar+2;<br /> void* varValue;<br /> object_getInstanceVariable(object,varName,&varValue);<br /> <br /> [(id)varValue release];<br /> } else {<br /> // a @property(retain) or @property(copy)<br /> // but without an instance variable attached... so what to do?<br /> }<br /> }<br /> }<br /> free(properties);<br />}<br />@end
Exemple d'utilisation :
@interface Dummy : NSObject {<br /> int _machin;<br /> NSArray* unTableau;<br /> IBOutlet UILabel* label;<br />}<br />@property(assign) int machin;<br />@property(retain) NSArray* truc;<br />@property(retain) NSString* labelText;<br />@end<br /><br />@implementation Dummy<br />@synthesize machin = _machin, truc = unTableau;<br />-(NSString*)labelText { return label.text; }<br />-(void)setLabelText:(NSString *)txt { label.text = txt; }<br /><br />-(void)dealloc<br />{<br /> [Dummy releasePropertiesOfObject:self]; // fait un release sur unTableau et _machin<br /> [super dealloc];<br />}<br />@end
Notes :
- J'ai pas trouvé de possibilité de présenter ma méthode sous forme d'une méthode d'instance, de sorte qu'on puisse l'appeler genre [tt][self releaseProperties][/tt]... car je n'ai pas trouvé de moyen de détecter la classe "logique" de l'objet self (seulement la classe réelle via [tt][self class][/tt])... ce qui pose problème quand la méthode est appelée récursivement via des [tt][super dealloc][/tt] puisque [tt][self class][/tt] retournera la même classe au fur et à mesure de la remontée dans la hiérarchie, donc boum...
- C'est pour ça que j'ai fait une méthode de classe, permettant d'avoir un accès à la classe "logique" et de parer à tous les cas genre [tt]Parent* p = [[[Enfant alloc] init] autorelease];[/tt]. Mais si vous avez une meilleure idée de ce côté...?
- Je me demande que faire dans le cas où l'on a une @property(retain) non associée à une variable d'instance (mais implémentée à la main), comme [tt]labelText[/tt] dans mon exemple ? Pour l'instant je fais rien, mais devrais-je affecter la propriété à nil ou autre ? Votre avis ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
P.S. j'ai jamais dit que j'étais pas flemmard moi
Ce que j'aime des @properties c'est que ça évite d'implémenter le setter et le getter, et en plus ça synthétise bien les propriétés (au sens commun du terme) de la classe, que tu vois du coup en un coup d'oeil, au lieu d'avoir plein de [tt]-(Type)machin[/tt] et [tt]-(void)setMachin:(Type)x[/tt].
Après, dans tous les cas de toute façon il faut faire un release sur tes variables d'instances dans ton dealloc, que tu utilises les @properties ou pas ! Donc ça ne change pas le problème de devoir faire tout plein de release dans ce dealloc...
Car en fait écrire une ligne @property T toto dans le .h pour moi est tout simplement exactement équivalent à écrire les 2 lignes [tt]-(T)toto[/tt] et [tt]-(void)setToto:(T)v[/tt], côté header. Et d'en profiter pour indiquer ta politique de gestion mémoire associée. Côté .m, ça te permet si tu le souhaites d'implémenter automatiquement le setter et getter... ou pas, comme tu veux !
Sauf que si tu n'utilises pas les @properties, tu seras aussi obligé de faire les release à la main. Alors que si tu utilises @property, avec mon code tu peux juste faire [tt][TaClasse releasePropertiesOfObject:self];[/tt] et ça le fait tout seul. Ce que tu ne peux encore pas faire si tu n'utilises pas les @property.
Après, chacun sa façon de coder, et chacun ses préférences, mais moi quand je compare ces 2 possibilités, mon choix est tout trouvé ;D
Enfin pour l'instant ça me dérange pas de faire un simple "self.toto=nil;" même si j'en ai 10 à mettre À force de tout masquer, on fini par ne plus s'en soucier, et ça je préfère pas
Parce que mettre des mutex quand il n'y en a pas besoin, c'est sacrement lourd !
Tu n'es pas au courant des nouveautés de Xcode 3.2 avec l'intégration de Clang/LLVM dedans, et maintenant tu n'es pas au courant de tous les avantages des @properties ? Mais ce n'est plus le schlum que je connais ça ;D
Oui @synthesize rajoute ce qu'il faut... en fonction des attributs que tu mets à ta @property bien sûr. Si tu veux pas les mutex, tu mets un [tt]nonatomic[/tt] et basta
Etc, etc, bref, justement ce qui est bien avec @property c'est que ça gère pour nous tous les cas tout seul sans rien oublier ;D C'est ce qui fait aussi la beauté des @property !
Mais après, si tu veux pas utiliser @synthesize, ça t'empêche pas d'utiliser @property (et d'implémenter ton setter et getter toi-même).
Et puis entre nous, tout ça combiné à mon petit "User Script" que j'ai rajouté à Xcode pour qu'il copie mes déclarations de variables d'instances sélectionnées et génère les @property(nonatomic, retain|assign) associés tout seul... y'a vraiment plus grand chose à taper comme code sérieux. Le temps que je gagne au niveau du dev de ce côté loin d'être négligeable avec tout ça
PS : J'ai édité mon exemple plus haut de comparaison avec/sans @property pour montrer 3 cas différents
ça peut être une bonne habitude que de pas trop compter sur les automatismes ...
En attendant tu utilises le Garbage Collector apparemment, non ? :P
[tt]self.toto = nil[/tt] permet en effet, puisqu'il appelle du coup le setter [tt][self setToto:nil];[/tt], de faire un release au final. Sauf que c'est ce que je faisais au début aussi... avant de lire des articles m'expliquant que ce n'était pas la meilleure des pratiques, et qu'il était préférable de faire un release sur la variable d'instance directement.
On avait eu une discussion sur PommeDev récemment à ce sujet d'ailleurs.
Dans les cas où les setters ont des mécanismes de dépendances (déclenchement des KVO, code perso si tu as personnalisé ton setter qui pourrait appeler d'autres objets... potentiellement déjà désalloués à ce moment dans le dealloc... ce genre de trucs) ça peut poser des problèmes et des prises de têtes. Alors qu'un appel direct au release, c'est plus simple/atomique comme action et avec moins de conséquence.
Bah justement, moi à force d'avoir des modifications de specs (saleté de MOA :P) et donc d'avoir à modifier parfois mes classes au fur et à mesure de l'avancement de mon projet, pour rajouter des variables d'instances par-ci par-là ... j'en arrive vite à oublier justement de releaser une variable que j'avais rajoutée (Et pour ça Clang loupe souvent le coche et ne me signale rien...). Et quand on commence à avoir 60 classes avec des v.i. retainé de partout et pas le temps de faire de la review de code, ...
Autant pour moi alors
Soit ma mémoire n'est pas si bonne, soit y'a eu des évolutions qui m'échappent encore.
PS pour les dents de la lumière des WC c'est fait :P
Ha.. tu m'as convaincu sur le coup
D'un autre côté ça nous permet aussi de porter plus d'attention à notre code :adios!: Mais bon j'avoue quand même être séduit.. Bien que ça me fasse un peu chier de devoir inclure ta catégorie de NSObject dans chacun des nouveaux projets. À force on se retrouve à inclure plein de fichiers de ce genre à chaque fois qu'on fait un nouveau projet, en plus des framework (Sparkle, etc..)
Tiens d'ailleurs, n'y a-til pas la possibilité de dire à XCode d'ajouter des fichiers directement à la création d'un projet de type iPhone ou Mac ?
La syntaxe pointée est apparue avec Objective-C 2.0. Depuis, on peut donc écrire [tt]self.toto = a;[/tt] qui est exactement équivalent à [tt][self setToto:a];[/tt], et écrire [tt]a = self.toto;[/tt] qui est exactement équivalent à [tt]a = [self toto];[/tt].
Après, la syntaxe pointée, on aime ou on aime pas (et on peut utiliser ou non, qu'on utilise les @property ou pas, ce sont deux choses décorrélées). Mais faut bien avoir en tête que [tt]self.toto = a[/tt] du coup est bien différent de [tt]toto = a[/tt] puisque dans le dernier cas ça fait une simple affectation... alors que dans le premier cas ça appelle le setter (qui peut très bien ne faire qu'une simple affectation, remarque, mais fait plus probablement un release de l'ancienne valeur, un retain de la nouvelle, etc... bref gère la mémoire comme il faut comme tout bon setter qui se respecte)
Merci Ali
C'est justement ce que je n'avais pas réalisé pleinement.
J'utilise cette syntaxe pointée et je joue même de la différence entre la simple affectation [tt]toto = a[/tt] et la pointée [tt]self.toto = a[/tt] pour shunter ou pas le setter et donc le KVObserving, mes chers binding et l'undoManager.
MAIS mon apréhension de la syntaxe n'avait pas été jusqu'à pleinement réaliser ce que faisait le setter obligeament mis à disposition par l'ObjC2. Plus exactement je le savais, mais ce n'était pas suffisemment assimilé par mon esprit pour avoir toujours conscience des implications.
MERCI !
C'est un argument de plus pour ce méfier des "boites noires" que l'on peut utiliser sans avoir complètement conscience de ce qu'elle font....
Pour avoir fait des tests entre modification directes, modifications " atomiques " (des libs atomic) et modifications avec mutex, il y a une perte d'optimisation très nette à chaque étape.
Et c'est quand même assez rare d'avoir besoin d'un mutex dans des setters / getters.
Et quand t'as besoin d'une gestion atomique sur un u64 et que tu ne veux pas de mutex, tu peux utiliser des fonctions atomiques automatiquement avec les "properties" ? ("man atomic") :P
Sinon, non, je n'utilise aucune nouveauté d'Objective-C 2.0, ça n'apporte pas grand chose d'intéressant ; au contraire, ça rend le code beaucoup moins lisible (à mon sens bien sûr)...
Par exemple, si on a une ivar mais pas de property dessus, il faut quand même penser à la releaser dans dealloc ? Il faudrait donc faire gaffe aux ivars sans ou avec property... Ou alors il release tout (et dans ce cas, ça peut être dangereux si l'objet n'a pas été alloué avec l'ivar dans le code ?)
Peut-être qu'ici y'a moyen d'afiner en testant le Runtime ?:
-> on la libère
[EDIT] Suis-je bête, pas besoin de tester le runtime puisque ton backingVar existera que l'ivar soit déclaré dans l'interface ou synthétisé par le runtime.
Reste donc le cas de figure de l'absence d'iVar.
Dans le doutes tu l'ignores c'est le mieux en effet.
T'as raison Schlum, et c'est documenté en effet:
Ce serait même un argument pour utiliser le Garbage Collector !! :P:
Je me faisai exactement la même réflexion y'a 5 minutes !!!
@Schlum : oui par contre je suis tout à fait d'accord, j'ai jamais compris pourquoi par défaut dans le langage ils avaient fait en sorte que par défaut les @property soient atomic (et qu'il faille indiquer "nonatomic" pour ne pas avoir les mutex), j'aurais pour ma part carrément préféré l'inverse aussi.
Bon en même temps maintenant je m'en fiche puisque je n'écris même plus ces lignes (je fais un Pomme-Alt-Ctrl-P sur mes ivar pour lesquelles je veux qu'elles soient des propriétées (ce qui n'est pas forcément le cas de toutes), et un Pomme-V et mes "User Scripts" que j'ai créé dans Xcode m'écrivent les lignes tout seuls donc je me pose plus la question moi Mais je suis bien d'accord, j'aurais préféré "nonatomic" par défaut, et un mot clé "atomic" possible si on veut les mutex, et pas l'inverse !
Ceci dit comme je le disais plus haut, l'un n'empêchant pas l'autre, tu n'es pas obligé d'utiliser toutes les nouveautés d'Objective-C 2.0 en même temps. Rien ne t'empêche de déclarer tes setters/getters avec une ligne @property (au lieu d'une ligne [tt]machin[/tt] et [tt]setMachin:[/tt]), mais de continuer de les implémenter toi-même (avec ou sans mutex selon tes besoins, avec ou sans KVO selon tes besoins, avec les fonctions de atomic.h ou pas, en utilisant une ivar ou pas si ta property est dérivée, ça n'empêche pas. C'est pas parce que tu déclares tes propriétées ayant un getter+un setter via @property au lieu de via [tt]machin[/tt] et [tt]setMachin:[/tt] que tu es obligé d'utiliser @synthesize dans le .m !!
@Alex : Oui ça ne libère que les @property déclarées retain ou release, pas du tout les autres ivar (où de toute façon par contre pour ça je trouve que ça serait une mauvaise idée, car comment savoir si tu as effectivement fait un retain sur ces ivars ? Parce que bon certes c'est souvent le cas, du moins pour les ivar de type NSObject, pas pour les types atomiques genre int ou float... Sauf pour les delegate, les dataSource, et autres références.... Bref pour moi ça c'est plutôt au cas par cas.
Un peu comme pour les @property... sauf que l'avantage des @property c'est justement qu'on indique aussi la politique/rôle qu'on donne à cette propriété (retain ou copy ou assign, etc) et que du coup là on sait qu'on respecte les règles mentionnées dans le .h avec ma méthode.
@Hervé : Oui, pour les @property non associées à une ivar (setMachin:(T)v et -(T)machin qui ne modifient ou retourne pas la valeur d'une ivar machin mais dérivent cette valeur ou font tout autre chose, quoi), c'est un peu des cas particulier et au cas par cas, donc c'est pour ça que je me suis dit comme toi, autant ne rien faire.
Le but de mon code releasePropertiesOfObject: c'est de remplacer parfois 10 ou 20 lignes de [machin release] par une seule ligne... Mais ça n'empêche pas qu'il faut écrire le [super dealloc], et releaser les ivar qui ne sont pas en @property... donc si y'a aussi des cas particulier à gérer, il est encore temps de les faire à la main au cas par cas aussi
Tiens, un article vient de tomber sur un blog :
http://cocoawithlove.com/2009/10/memory-and-thread-safe-custom-property.html
On y parle des méthodes accesseurs personnalisées et du "type" atomique ou non. Pas de lien direct avec ton sujet, mais cela complète certainement ton passage sur "atomic"... ou pas.
Voilà , ça peut vous intéresser également.
Merci Muqaddar
Je découvre l'intérêt des [tt]retain[/tt]/[tt]autorelease[/tt] des properties atomic et j'avoues que, sans le [tt]@synthétize[/tt], j'oublirai souvent de faire ainsi ...