[Tuto/Cours] Variables d'instance, propriétés et accesseurs

muqaddarmuqaddar Administrateur
novembre 2009 modifié dans Objective-C, Swift, C, C++ #1
Suite à  une petite discussion avec Ali tout à  l'heure, je voulais avoir votre avis sur la façon d'écrire votre code quand vous créez ou modifier un objet.

J'utilise souvent des propriétés qui portent le même nom que mes variables d'instances (je pense que 95% des développeurs font ainsi).

Par exemple, dans mon .h, j'ai la variable d'instance :

FMDatabase *_dataDB;


et sa propriété :

@property (nonatomic, retain) FMDatabase *_dataDB;


Jusque là , tout va bien.  :)

Dans mon .m, je crée mon objet, et pour ce, j'ai plusieurs façons de le faire.

Si j'oublie mon accesseur, je dois me taper le retain :

_dataDB = [[FMDatabase databaseWithPath:kDATA_PATH] retain];


et en plus à  moi de gérer les futurs releases ou retain, en fonction de l'utilisation de l'objet dans la suite de mon code.

J'utilise alors mon accesseur (ma propriété). Pour cela j'ai 2 solutions.

1) La plus ancienne :

[self set_dataDB:[FMDatabase databaseWithPath:kDATA_PATH]];


2) Et la nouvelle, disponible depuis objective-C 2.0 je crois :

self._dataDB = [FMDatabase databaseWithPath:kDATA_PATH];


Ces 2 façons de faire font évidemment un retain puisqu'elles utilisent les accesseurs dont c'est le boulot (puisque demandé dans la propriété).

Or, pour moi, autant dans la façon ancienne (1) je me dis qu'il fait naturellement un retain. Autant dans la nouvelle (2), ça me choque de ne pas voir ce fameux retain écrit après l'objet autoreleasé (databaseWithPath). Evidemment il ne faut pas le mettre sinon on aura une fuite mémoire (+1), mais c'est juste que ça me fait bizarre de ne pas voir ce retain juste à  côté...

A la limite, ça me rassurerait de pouvoir voir directement dans le nom d'une propriété s'il y a un retain ou pas. N'y a-t-il pas une convention de nommage là -dessus ?

«1

Réponses

  • AliGatorAliGator Membre, Modérateur
    21:35 modifié #2
    Alors :

    1) Ca me choque toujours autant que tu mettes
    _dataDB = [[FMDatabase databaseWithPath:kDATA_PATH] retain];
    
    sans avoir écrit [tt][_dataDB release][/tt] avant. Bon certes, dans certains cas (genre par exemple dans le init ou viewDidLoad ou quoi), tu sais qu'il était nil au moment de cet appel, donc tu peux te dire que c'est inutile de faire un release... Mais bon, ça coûte pas grand chose de le faire quand même, pour plus de cohérence (puisque, comme quand tu codes un setter, il faut faire un release de l'ancienne valeur avant de retainer la nouvelle), puisqu'un message à  nil est ignoré, donc faire [tt][_dataDB release][/tt] si [tt]_dataDB[/tt] est nil à  ce moment la n'a aucune effet... Par contre si par la faute à  pas de bol il n'est pas nil, au moins ça ne fera pas de fuite mémoire.

    2) J'ai du mal à  comprendre pourquoi ça te choque dans un cas ([tt]self._dataDB = ...[/tt]) et pas dans l'autre ([tt][self set_dataDB:...][/tt]). C'est sans doute que je suis habitué et que moi ça ne me choque plus, j'en sais rien... mais en fait surtout ce que je ne capte pas c'est pourquoi, pour que ça te choque moins, tu voudrais faire un retain car tu dis que ça te manque de pas le voir explicitement... mais que par contre ça ne te choque pas du tout de ne pas avoir de release, alors qu'il est tout aussi important et que j'ai quand même l'impression que tu l'oublies souvent (puisque tu l'as passé sous silence aussi lors de notre discussion MSN...), comme si tu pensais qu'il était secondaire, ou que tu ne le faisais pas systématiquement...



    Après, comme je t'ai dit, rien ne t'oblige à  nommer tes propriétés et tes variables d'instances de la même manière. D'ailleurs justement moi quand je veux éviter la confusion, j'utilise souvent des noms différents, avec un underscore pour l'ivar, et sans pour la @property.

    Pour rappel, déclarer une [tt]@property Type nom[/tt] revient à  la même chose que de déclarer le setter [tt]-(void)setNom:(Type)newValue;[/tt] et le getter [tt]-(Type)nom;[/tt].
    Bon sauf qu'en plus cela a d'autres avantages, comme l'introspection de type, ou le fait que le code pour ces setters et getters peut si l'on veut être généré automatiquement via @synthesize, bien que ça ne soit pas une obligation, ainsi aussi que l'indication de la politique d'accès mémoire, comme (retain) ou (assign) ou (copy). Mais sinon principalement ça déclare ces setters et getters.
    - Donc d'une part ces setters ou getters vont faire le retain et release à  notre place s'ils sont bien codés et sont bien sensés retenir l'objet et non pas juste l'assigner (comme c'est le cas pour les delegate en general, ou évidemment pour les variables de type non-objet comme les int ou float)
    - Et aussi on peut tout à  fait donner à  ces setters ou getters des noms différents des variables d'instances que l'on veut associer
    - On peut même ne pas associer du tout de variable d'instance à  ces @property, oui oui, ce n'est pas obligatoire non plus. Ce sont juste des méthodes setNom: et nom, mais qui peuvent en réalité par exemple envoyer une requête réseau pourquoi pas, ou aller lire dans les UserDefaults, ou stocker cette valeur dans un autre objet (NSDictionary, ...), ou que sais-je encore.

    Je dis ça parce que beaucoup (moi le premier au début avant que je comprenne bien tout ça) mettent dans le même panier @property, @synthesize, et la "syntaxe pointée" (self.machin) d'Objective-C 2.0, alors que les 3 peuvent être utilisés indépendamment.

  • AliGatorAliGator Membre, Modérateur
    novembre 2009 modifié #3
    Mini-cours sous forme d'exemples divers et variés pour illuster tout cela :
    (c'est un peu dense comme post mais parce que j'explique chacun des 5 cas illustrés pour montrer les différences) :
    @interface Dummy : NSObject<br />{<br />  NSString* name; // (1a)<br />  NSString* _dbPath; // (2a)<br />  IBOutlet UILabel* textLabel; // (3a)<br />  UIActivityIndocatorView* _activityView; // (4a)<br />}<br />@property(nonatomic,retain) NSString* name; // (1b)<br />@property(nonatomic,retain) NSString* dbPath; // (2b)<br />@property(nonatomic,retain) NSString* text; // (3b)<br />@property(nonatomic,retain) IBOutlet UIActivityIndicatorView* activityView; // (4b)<br />@property(nonatomic,assign, getter=isLoading) BOOL loading; // (5a)<br />@end
    
    @implementation Dummy<br />@synthesize name; // (1c)<br />@synthesize dbPath=_dbPath; // (2c)<br />-(NSString*)text { return textLabel.text; } // (3c)<br />-(void)setText:(NSString*)v { textLabel.text=v; } // (3d)<br />@synthesize activityView = _activityView; // (4c)<br />-(void)setLoading:(BOOL)v { // (5b)<br />  if (v) [_activityView startAnimating];<br />  else [_activityView stopAnimating];<br />}<br />-(BOOL)isLoading { return [_activityView isAnimating]; } // (5c)<br />-(void)dealloc<br />{<br />  [name release];<br />  [dbPath release];<br />  [_activityView release];<br />  [super dealloc];<br />}<br />@end
    
    • 1. On a une ivar de type NSString nommée "name" (1a), et sa @property se trouve avoir le même nom (1b), cas somme toute assez courant/usuel. De plus, on a demandé au compilateur de générer automatiquement le code pour ces setters et getters (1c), le code du setter se chargera alors automatiquement de faire le release de l'ancienne valeur et le retain de la nouvelle puisque la propriété a déclaré qu'elle utilisait la politique du (retain) dans ses attributs (1b). Si on avait mis (assign) à  la place à  cet endroit, le setter se serait contenté d'une simple affectation bien sûr.

      - Dans ce cas, on peut modifier la variable d'instance directement par [tt]name = @toto;[/tt], mais cela ne fera aucun retain ni release bien sûr, donc mènera dans le cas présenté en généralà  une fuite mémoire (sauf si on sait ce qu'on fait et qu'on s'assure d'avoir bien fait les release nécessaires puis les retains nécessaires aussi, ..)
      - Ou on peut utiliser le setter explicitement, avec [self setName:@toto], qui lui, puisqu'on utilise le setter, fera bien un release de l'ancienne valeur et un retain de la nouvelle, tout ça de façon transparente, c'est un setter, c'est son rôle.
      - Ou alors on peut utiliser aussi la nouvelle notation pointée d'Objective-C 2.0, [tt]self.name=@toto[/tt], qui est strictement équivalente à  la précédente [tt][self setName:@toto][/tt] (en réalité la notation pointée est convertie en appel au setter ou au getter au moment de la compilation, et non au runtime, d'ailleurs). Mais il ne faut du coup pas confondre [tt]self.name = @toto[/tt] et [tt]name = @toto;[/tt], le premier faisant appel au setter, le second faisant une bête affectation de variable d'instance, donc avec potentiels leaks.

    • 2. C'est pourquoi j'utilise parfois un nom différent pour la variable d'instance et la @property, comme dans le cas (2a)/(2b). On déclare une variable d'instance [tt]_dbPath[/tt] d'un côté, et une [tt]@property dbPath[/tt] de l'autre, donc le setter et le getter déclarés par cette dernière, s'appelleront [tt]dbPath[/tt] et [tt]setDbPath:[/tt], sans le underscore. Et pour dire au compilateur qu'on veut qu'il génère automatiquement le code des méthodes [tt]-dbPath[/tt] et [tt]-setDbPath:[/tt], on utilise comme toujours @synthesize, mais là  on précise au compilateur qu'il faut qu'il s'appuye sur la variable d'instance nommée, elle, [tt]_dbPath[/tt] (et non [tt]dbPath[/tt] sans underscore). D'où la ligne (2c).

    • 3. Dans le cas 3, on a encore plus de décorellation : on a une ivar "textLabel" d'un côté (3a), qui en plus est déclaré comme IBOutlet, et des méthodes [tt]-text[/tt] et [tt]-setText:[/tt], déclarées via le @property de l'autre (2b), qui vont venir modifier le texte de ce textLabel. Dans ce cas :
      - le IBOutlet étant devant la ivar, dans IB on va se voir proposer un outlet nommé "texLabel", du nom de l'ivar devant laquelle on a mis le mot clé
      - cette fois on ne demande pas au compilo de générer le code des méthodes text et setText tout seul, mais on les implémente nous-même (3c, 3d), de sorte que l'appel à  ces méthodes modifie le texte du UILabel.
      - Ainsi, on peut indifféremment appeler [tt][self setText:@toto][/tt] ou [tt]self.text=@toto[/tt] (ces deux syntaxes étant exactement équivalentes) pour modifier le texte du textLabel.
      - On peut aussi si l'on veut (mais uniquement depuis le code de la classe évidemment pour avoir accès à  ses ivars), écrire textLabel.text = @toto (mais on ne pourra pas faire ça depuis une autre classe que la classe Dummy, autre classe qui n'aura pas accès à  l'ivar textLabel, mais pourra appeller setText par contre sur un objet Dummy)

    • 4. Le cas 4 est un mix du cas 2 et du cas 3 : on a une @property qui a un nom différent de la variable d'instance, et on demande de générer le code des setter et getter automatiquement, en indiquant le nom de l'ivar sur laquelle doit se baser le setter (4c) puisqu'ils ont des noms différents.
      Par contre cette fois, le mot clé "IBOutlet" est sur la @property nommée activityView, ce qui permet de présenter dans IB l'outlet sous ce nom. Si on avait mis le mot clé IBOutlet sur l'ivar _activityView, on aurait vu comme nom pour l'outlet dans IB "_activityView" et non "activityView", ce qui est quand même moins joli :P
      En plus, puisqu'il existe un setter associé, lors du désarchivage du NIB, c'est le setter qui va être appelé pour affecter le contrôle UIActivityIndicatorView à  cette variable _activityView, via justement ce setter setActivityView: désigné par la @property...

    • 5. Pour le cas 5, on voit que l'on a déclaré une @property qui n'a aucune "backing variable" (à  laquelle aucune variable d'instance n'est associée). D'ailleurs on ne pourrait pas faire de @synthesize das ce cas (car le compilo essayerait de générer du code qui va assigner la nouvelle valeur à  une ivar nommée "loading" qui n'existe pas, et va gueuler sur l'inexistance de cette "backing variable" / ce "backing store" où stocker la valeur.
      Mais rien n'empêche de déclarer cette @property comme je l'ai fait en (5b) et (5c), même si elle n'a ni variable d'instance associée ni que son code est généré automatiquement via @synthesize, mais qu'on écrit le code nous-même (en l'occurence ça pilote l'activityView comme vous pouvez le voir ou retourne son état).
      De plus, dans ce cas 5, on voit que j'ai redéfini le nom du getter associé à  cette @property, c'est quand même plus sympa d'avoir un getter nommé "isLoading" que juste "loading" pour avoir l'état ;)




    Ce qu'il faut retenir de tous ces exemples :
    - @property est souvent lié à  une ivar mais ce n'est pas obligatoire
    - si c'est le cas, l'ivar n'est pas obligée d'avoir le même nom que la @property, ce qui peut éclaircir les choses au début pour mettre en avant la différence entre l'ivar et la propriété si vous n'y voyez pas clair
    - utiliser @property n'implique pas forcément d'utiliser @synthesize, on peut aussi coder nos accesseurs nous-mêmes
    - la syntaxe pointée est indépendante des @property
    - la syntaxe pointée est exactement équivalente à  appeler le setter ([tt]self.machin = truc[/tt] vaut [tt][self setMachin:truc][/tt]) ou le getter ([tt]truc = self.machin[/tt] vaut [tt]truc = [self machin][/tt])
    - ne pas confondre l'accès direct à  une ivar (_dbPath) avec la syntaxe pointée pour appeler une @property (self.dbPath), en particulier l'affectation d'une ivar comme [tt]dbPath=@toto.db[/tt] ne fait pas de release/retain, mais juste une affectation, alors que la syntaxe pointée [tt]self.dbPath=@toto.db[/tt], étant exactement équivalente à  [tt][self setDbPath:@toto.db][/tt], fait bien appel au setter associé à  la variable, qui donc se charge de faire le release de la variable précédente et le retain de la nouvelle


    Au final, si tu accèdes directement à  l'ivar, ça fait une affectation, donc aucun appel à  un quelconque release ni retain, et si tu utilises les @property(retain), que ce soit via la syntaxe pointée self.dbPath=... ou par l'appel direct à  setDbPath:..., ça fait le release et ça aussi le retain, comme ça a toujours été le sensé être le cas pour tout setter bien codé voulant utiliser la politique du retain/release avec sa variable.
  • muqaddarmuqaddar Administrateur
    novembre 2009 modifié #4
    Merci Ali ! Quel cours magistral ! :o

    Je ne savais pas qu'on pouvait faire autant de choses avec les propriétés, et que leur nom pouvait se détacher complétement de celui de la variable d'instance déclarée qu'ils retain/release.

    Pour les outlets, je m'étais souvent demandé pourquoi ils étaient parfois déclarés avec l'ivar et parfois avec la propriété. Maintenant je sais !

    Je pense que je vais m'habituer à  la syntaxe pointée, mais sans changer le nom des accesseurs (sans underscore). J'aime quand même bien voir mon undescore, je sais qu'il ne s'agit ainsi pas de variables de méthodes.

    Super merci en tout cas, je vais m'autoriser plus de liberté maintenant.

    EDIT : à  vrai dire, des choses paraissaient plus évidentes et compréhensibles avant l'introduction des @property et @synthesize. On devait coder les accesseurs en dur. Mais pour rien au monde je voudrais y revenir puisque c'est un gain de temps conséquent, sauf dans le cas où l'on doit les customizer comme tu l'as montré.
  • muqaddarmuqaddar Administrateur
    novembre 2009 modifié #5
    Voilà , je viens de commencer à  modifier mon AppDelegate pour avoir plus de cohérence :

    @interface MyAppDelegate : NSObject &lt;UIApplicationDelegate&gt; <br />{&nbsp; &nbsp; <br />	UIWindow *_window;<br />	UINavigationController *_navigationController;	<br />	FMDatabase *_dataDB;<br />	NSString *_currentLanguage;<br />}<br /><br />@property (nonatomic, retain) IBOutlet UIWindow *window;<br />@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;<br /><br />@property (nonatomic, retain) FMDatabase *_dataDB;<br />@property (nonatomic, retain) NSString *_currentLanguage;
    


    <br />#pragma mark -<br />#pragma mark accessors<br /><br />@synthesize window = _window;<br />@synthesize navigationController = _navigationController;<br />@synthesize _dataDB;<br />@synthesize _currentLanguage;<br /><br />#pragma mark -<br />#pragma mark application delegate<br /><br />- (void)applicationDidFinishLaunching:(UIApplication *)application <br />{	<br />	// CONFIGURE IPHONE UI<br />	//<br />	// set the new status bar style<br />	[application setStatusBarStyle:UIStatusBarStyleBlackOpaque];<br />	<br />	// set background window image<br />	UIView *backgroundView = [[UIView alloc] initWithFrame:self.navigationController.view.frame];<br />	backgroundView.backgroundColor = kBG_COLOR;<br />	[self.window addSubview:backgroundView];<br />	[backgroundView release];<br />	<br />	// set the navigationController<br />	self.navigationController.view.backgroundColor = kCLEAR_COLOR;<br />	self.navigationController.navigationBar.tintColor = kNAVBAR_COLOR;<br />	<br />	// set the window<br />	[self.window addSubview:self.navigationController.view];<br />	[self.window makeKeyAndVisible];	<br />}<br /><br />#pragma mark -<br />#pragma mark memory<br /><br />- (void)dealloc <br />{<br />	[_navigationController release];<br />	[_window release];	<br />	[_dataDB release];<br />	[_currentLanguage release];	<br />	[super dealloc];<br />}<br />
    


    Ainsi, j'ai bien mes 4 ivars déclarées avec underscore, et pour 2 d'entre elles j'ai un outlet donc je renomme l'accesseur sans undescore et attribue la variable d'instance avec undescore.

    Côté IB, j'ai bien mes outlets sans undescore, et dans la suite de mon code, je me sers de la syntaxe pointée.

    EDIT : En fait j'ai un doute. Est-ce qu'il faut mieux que j'écrive du coup :

    self.navigationController.view.backgroundColor = kCLEAR_COLOR;
    


    ou

    _navigationController.view.backgroundColor = kCLEAR_COLOR;
    


  • NseaProtectorNseaProtector Membre
    novembre 2009 modifié #6
    Bonjour, et d'abord merci pour ce post.
    iVar c'est bien variable d'instance ?
    edit : oui

    Et l'underscore devant le nom il a une utilité particulière ou bien il sert juste pour "repère", est-il prit en compte ou ignoré par le compilateur ?

    edit: je crois comprendre (exemple 2) qu'il te sert juste de repère (on pourrait mettre iVarDbPath à  la place de _dbPath) du moment que l'on écrive:
    @synthesize dbPath= iVarDbPath;

    Je vais relire car c'est un peu hard a avaler des le matin.
    Encore un grand MERCI.
  • allianallian Membre
    21:35 modifié #7
    Merci Ali, ton post m'a éclairé sur pas mal de points.
  • ClicCoolClicCool Membre
    21:35 modifié #8
    Perso ce qui me gène c'est de déclarer une ivar avec le underscore, donc selon les conventions de nomage strictement privée, puis de lui assigner des getter/setters rendant donc cette ivar accessible à  l'extérieur de la classe.
    Peu importe qu'on renomme l'accès à  cette ivar dans une déclaration @property histoire de virer le undescore. Effectivement ça permet bien de distinguer rapidement le type d'accès, direct ou par setter, qu'on y fait. Mais ça rend implicitement cette ivar directement accessible de l'extérieur.
  • muqaddarmuqaddar Administrateur
    21:35 modifié #9
    dans 1258017134:

    Perso ce qui me gène c'est de déclarer une ivar avec le underscore, donc selon les conventions de nomage strictement privée, puis de lui assigner des getter/setters rendant donc cette ivar accessible à  l'extérieur de la classe.
    Peu importe qu'on renomme l'accès à  cette ivar dans une déclaration @property histoire de virer le undescore. Effectivement ça permet bien de distinguer rapidement le type d'accès, direct ou par setter, qu'on y fait. Mais ça rend implicitement cette ivar directement accessible de l'extérieur.



    L'undescore est une convention de nommage privée ? Ce n'est pas juste une convention de nommage des ivars pour les différencier des vars de méthode ?

    J'ai toujours entendu ou lu :
    - pas d'undescore => variable de méthode
    - 1 undescore => variable d'instance
    - 2 undescores => variable de classe


  • ClicCoolClicCool Membre
    novembre 2009 modifié #10
    Les conventions de nomage des variables d'instances de la doc Apple ne font pas du tout état des underscore.
    The convention for naming instance variables is to make them a lowercase string containing no punctuation marks or special characters. If the name contains multiple words, run them together, but make the first letter of the second and each succeeding word uppercase. For example:
    NSString *title;
    UIColor *backgroundColor;
    NSRange currentSelectedRange;


    Mais tu as raison, Apple, dans ses headers, déclare toutes les variables d'instances de ses classe en @private et elles sont toutes "underscorées".

    Perso, je trouve que de mettre un underscore à  toutes les variables d'instances de nos classes n'a pas de sens et ne permet plus de distinguer quoique ce soit.
    Autant n'en mettre aucun.
    Et c'est du reste ce qu'Apple nous incite à  faire dans la citation que j'ai mise au dessus.
    C'est aussi ainsi qu'ils font tout au long des exemples de code qu'il nous propose du reste ... (au moins c'est plus facile à  écrire et à  lire)

    Perso, j'aime ni le tout ni le rien, et je trouve logique de se servir du underscore pour  qu'on distingue du premier coup d'oeil une simple variable d'instance d'une variable au comportement privé nécessitant un traitement spécial:

    Par exemple, une ivar underscorée serait plutôt à  mon sens de ce type:
    Dans le .h
    @interface MyAppDelegate : NSObject<br />{<br />  Bool _simplisticPreferences; <br />.../...<br />}
    

    Dans limplémentation:
    @implementation MyAppDelegate<br />- (BOOL) isSimplistic {return _simplisticPreferences}<br />- (void) setSimplistic: (BOOL) simplistic {<br />	if ( simplistic==[self isSimplistic) {<br />		// on ne fait rien}<br />		else {<br />			if (simplistic) {<br />				// Code pour diminuer les choix de la fenêtre préférences<br />			}<br />			else {<br />				// Code pour passer aux préférences évoluées<br />			}<br />			_simplisticPreferences = simplistic;<br />		}<br />	}<br />}<br />@end
    


    Là  ce n'est plus un "bête" setter et pas question qu'un autre objet modifie l'ivar _simplisticPreferences seulement sans que l'AppDelegate n'ai modifié l'interface en conséquence ...

    Une question d'habitude et/ou de goûts peut-être ?

    [EDIT] je me rends compte que mon exemple laisse penser que [tt]-setSimplistic[/tt] est un setter alors que le plus souvent ici ce serait plutot une IBAction genre [tt]-(void) togglePrefMode:(id) sender[/tt] (écrite différemment du reste...) qui ferait changer la valeur de [tt] _simplisticPreferences[/tt] ...
  • dilarogadilaroga Membre
    21:35 modifié #11
    EDIT : En fait j'ai un doute. Est-ce qu'il faut mieux que j'écrive du coup :

    Code: [Sélectionner]
    self.navigationController.view.backgroundColor = kCLEAR_COLOR;

    ou

    Code: [Sélectionner]
    _navigationController.view.backgroundColor = kCLEAR_COLOR;


    Le résultat sera le même mais dans le deuxième cas tu ne profiteras pas des effets de la synthetisation de ta propriété (implementation KVC, synchronisation si atomic, etc...).

    Une autre solution pour rendre les accesseurs des variables d'instance plus commodes à  lire est d'utiliser les options de renommage des propriétés :
    <br />@property (nonatomic,retain,getter=currentLanguage,setter=setCurrentLanguage) _currentLanguage ;<br />
    

    Ceci est très utile pour les ivar de type booléen...

    J'ai toujours entendu ou lu :
    - pas d'undescore => variable de méthode
    - 1 undescore => variable d'instance
    - 2 undescores => variable de classe


    Les conventions de code sont très diverses et dépendent fortement du langage pour lequel elles ont été pensées et des programmeurs qui les ont elaboré. Alors pour un même langage on aura souvent plusieurs conventions (et souvent même de belles querelles entre les membres qui les defendent). Ainsi rien ne t'empêche d'en établir une que tu respectes en veillant à  ce qu'elle rende le code plus lisible et non le contraire :)
  • muqaddarmuqaddar Administrateur
    novembre 2009 modifié #12
    Le résultat sera le même mais dans le deuxième cas tu ne profiteras pas des effets de la synthetisation de ta propriété (implementation KVC, synchronisation si atomic, etc...).


    Oui, je savais que le résultat serait le même dans ce cas  ;), je suppose donc que l'écriture avec self est conseillée... ? (sinon à  quoi bon avoir des accesseurs getter qui ne sont pas utilisés ?)
  • ClicCoolClicCool Membre
    21:35 modifié #13
    dans 1258037082:
    .../...(sinon à  quoi bon avoir des accesseurs getter qui ne sont pas utilisés ?)


    En réfléchissant à  ta phrase je me rend compte que les accesseurs générés automatiquement et KVO compliants sont très puissants et peuvent rendre le code d'implémentation d'une classe plus propre et lisible mais introduisent en même temps une sorte de confusion possible dans le code, voire une faille dans la sacro-sainte encapsulation.
    Si le @synthétize est fort commode et proprement codé, leur côté KVO compliant augmente encore l'exposition des variables d'instance à  l'extérieur de la classe. Ils autorisent, en particuliers, un binding sur ces variables permettant du coup de les modifier directement de façon parfois imprévue à  l'origine quand on a implémenté la classe.
    Ainsi des accesseurs que l'on aura mis en place pour faciliter le code de la classe peuvent, s'avérer d'une portée inattendue.
    Peut-être qu'il faudrait un @ synthétize (private) générant des accesseurs non KVO pour usage exclusif de la classe permettant juste à  la classe d'acceder proprement à  ses ivar sans pour autant les exposer au reste de l'appli ...? ? ?
  • dilarogadilaroga Membre
    21:35 modifié #14
    je suppose donc que l'écriture avec self est conseillée

    Dans un environnement orienté objet c'est conseillé, l'encapsulation étant un principe clé de la POO. Après avec l'objective-C v2 on peut choisir entre la notation traditionnelle (crochetée) ou la notation pointée. Mais dans tous les cas il vaut mieux éviter d'accéder directement aux variables d'instances.

    Peut-être qu'il faudrait un @ synthétize (private) générant des accesseurs non KVO pour usage exclusif de la classe permettant juste à  la classe d'acceder proprement à  ses ivar sans pour autant les exposer au reste de l'appli ...? ? ?

    L'implémentation des propriétés en objective-C 2 permet de réaliser assez finement ses choix de conception. On peut par exemple definir une propriété readonly dans l'interface publique d'une classe et la redefinir en readwrite dans une extension de cette même classe (interface privée) :
    interface :
    <br /><br />@interface MaClasse : NSObject<br />{<br />&nbsp; &nbsp; BOOL maPropriete ;<br />}<br /><br />@property(readonly) BOOL maPropriete ;<br /><br />@end<br />
    

    Implementation :
    <br /><br />@interface MaClasse()<br /><br />@property(readwrite) BOOL maPropriete ;<br /><br />@end<br /><br />@implementation MaClasse<br /><br />@synthesize maPropriete ;<br /><br />@end<br /><br />
    


    Ainsi tu auras un getter public et un setter privé.

    On peut encore réécrire le corps des setter/getter en utilisant le mot clé @dynamic en lieu et place de @synthesize.
  • ThibautThibaut Membre
    21:35 modifié #15
    Et lorsque l'on souhaite un getter dynamique mais un setter personnalisé, on fait comment ?

    J'utilise @synthesize et je surcharge le setter. Le problème c'est que le @synthesize génère déjà  un setter, que je vais redéfinir. Est-ce qu'il y a une meilleure méthode ?
  • muqaddarmuqaddar Administrateur
    21:35 modifié #16
    Voilà  le lien officiel de la doc Apple sur tout cela.

    Hervé, je n'ai rien vu pour indiquer aux propriétés d'être privées, mais cependant n'est-ce pas plutôt aux variables d'instances d'être privées plutôt qu'aux propriétés modifiant ces mêmes variables d'instance ?

    Sinon, comme le dit dilaroga, le fait que tu puisses mettre des propriétés en readonly  et les setter en interface privée protège pas mal...
  • muqaddarmuqaddar Administrateur
    21:35 modifié #17
    dans 1258043345:

    Et lorsque l'on souhaite un getter dynamique mais un setter personnalisé, on fait comment ?

    J'utilise @synthesize et je surcharge le setter. Le problème c'est que le @synthesize génère déjà  un setter, que je vais redéfinir. Est-ce qu'il y a une meilleure méthode ?


    Comme ça :

    @property (nonatomic, retain , setter=myPropSetter);
    


    ce qui permet d'écrire son propre setter sans que soit généré automatiquement le setter dynamique.
  • ThibautThibaut Membre
    21:35 modifié #18
    Même si on ne définit pas un nom particulier au setter ?

    Par exemple :
    @interface MaClasse {<br />BOOL _maPropriete;<br />}<br /><br />@property(readwrite,setter=setMaPropriete) BOOL maPropriete ;<br /><br />@end<br /><br />@implementation MaClasse<br /><br />@synthesize maPropriete;<br /><br />- (BOOL)setMaPropriete:(BOOL)aValue {<br />// setter perso<br />}<br /><br />@end<br />
    
  • dilarogadilaroga Membre
    novembre 2009 modifié #19
    Et lorsque l'on souhaite un getter dynamique mais un setter personnalisé, on fait comment ?

    Bonne question, je n'ai encore jamais essayé mais en definissant une propriété en readonly et en implémentant une methode correspondant à  son setter...


    Code: [Sélectionner]
    @property (nonatomic, retain , setter=myPropSetter);

    ce qui permet d'écrire son propre setter sans que soit généré automatiquement le setter dynamique.

    Non, le setter va être implicitement généré mais c'est le nom de la méthode pour l'appeler qui va être modifié.


    [EDIT] :
    @thibaut
    <br />@interface MaClasse <br />{<br />&nbsp; &nbsp; BOOL maPropriete;<br />}<br /><br />@property(readonly) BOOL maPropriete ;<br /><br />- (void)setMaPropriete:(BOOL) flag ;<br /><br />@end[code]<br />
    
  • ThibautThibaut Membre
    21:35 modifié #20
    En effet, c'est aussi bête que ça. J'avais peur que le self.maPropriete ne fonctionne pas, mais en fait ça passe bien.
    Merci !
  • muqaddarmuqaddar Administrateur
    21:35 modifié #21
    dans 1258044946:

    Non, le setter va être implicitement généré mais c'est le nom de la méthode pour l'appeler qui va être modifié.


    Merci, je ne l'avais pas compris dans ce sens ! ;)
  • ClicCoolClicCool Membre
    21:35 modifié #22
    dans 1258043361:

    Voilà  le lien officiel de la doc Apple sur tout cela.

    Hervé, je n'ai rien vu pour indiquer aux propriétés d'être privées, mais cependant n'est-ce pas plutôt aux variables d'instances d'être privées plutôt qu'aux propriétés modifiant ces mêmes variables d'instance ?

    Sinon, comme le dit dilaroga, le fait que tu puisses mettre des propriétés en readonly  et les setter en interface privée protège pas mal...


    Tu as raison, c'est bien des ivar privée que je parle.
    Mais si, meme en changeant le nom de la propriété, il en existe une qui y accède directement ça peut être un problème.

    En fait c'est très valable si tu veux, par exemple, juste assurer la pérénité d'une classe avec dans l'idée qu'au besoin dans l'avenir tu peux changer le nom de l'ivar ou son type et modifiera le setter en conséquence.
    L'ivar reste bien privée, le setter publique, et tu te charge du reste.

    Par contre ce n'est plus valable si tu définis les propriété par commodité pour le code de la classe mais que tu veux véritablement qu'aucune classe extérieure n'accède aux getter/setter.
  • ClicCoolClicCool Membre
    21:35 modifié #23
    dans 1258042555:
    .../...L'implémentation des propriétés en objective-C 2 permet de réaliser assez finement ses choix de conception. On peut par exemple definir une propriété readonly dans l'interface publique d'une classe et la redefinir en readwrite dans une extension de cette même classe (interface privée) .../...


    Es-tu sûr que de cette façon le setter ne sera pas accessible de l'extérieur ?

    Il me semble qu'il sera simplement caché aux yeux de ceux jettant un oeil au Header, auquel cas:
    • rien n'empèche un petit malin de trouver cette fonctionnalité "undocumented" à  ses risques et péril bien sûr.
    • Mais pire, il peut laisser penser que la propriété est bien en lecture seule et inciter à  utiliser, sans précaution, un binding dessus "en toute sécurité" alors que in-finé ce binding permettra bel et bien l'écriture sans avertir quiconque....
  • AliGatorAliGator Membre, Modérateur
    21:35 modifié #24
    Oui mais de toute façon avec le KVC on peut déjà  tout faire (surtout que +[NSObject accessInstanceVariablesDirectly] retourne YES par défaut).

    Même si tu n'as pas de setter, dans ce cas rien ne t'empêche de toute façon de faire [tt][monObject setValue:@toto forKey:@privateIVarWithoutSetter][/tt].

    Sauf si en plus de ne pas mettre de setter, tu surcharges +accessInstanceVariablesDirectly pour qu'il retourne NO...


    Sinon, dans tous les cas, à  partir du moment où tu déclares une méthode qui pourra modifier ta variable, même si elle est que dans le .m, si tu connais son nom tu pourras toujours l'appeler, même si c'est dans une extension privée (utiliser [tt]@interface MaClasse()[/tt] dans le .m pour définir des méthodes privées cachées du .h) : donc tu pourras appeler directement la méthode du moment que tu connais son nom, ou si tu veux éviter les warnings, utiliser l'aspect dynamique d'Objective-C et par exemple [tt][toto performSelector:@selector(privateSetter:) withObject:newValue];[/tt], ... bref de par l'aspect dynamique d'Objective-C, y'aura toujours un moyen de contourner si on veut... Mais bon faut être quand même assez tordu pour faire ça alors que c'est pas prévu de base par l'API et le .h de ta classe... (et à  solution capilotractée, résultats potentiellements dangereux et capilotractés, mais si tu les utilises, faut assumer les risques :P)
  • muqaddarmuqaddar Administrateur
    21:35 modifié #25
    Qui aurait un exemple de cas d'utilisation où il est fortement recommandé d'utiliser une interface privée pour déclarer des méthodes privées dans le .m ?

    D'autre part, sur un projet solo est-ce utile ? Où bien est-ce juste une sécurité par rapport à  nos propres erreurs possibles ? (je penche pour le cas 2)
  • AliGatorAliGator Membre, Modérateur
    novembre 2009 modifié #26
    Tu veux dire un cas concret qui m'était arrivé justement ? ;D Enfin c'est pas tout à  fait le cas, mais ça cause d'utiliser le readonly dans une classe et readwrite dans une classe fille...


    Sinon pour quand on est dans la même classe, ne définir le setter que dans une interface privée, ça ne m'est encore jamais arrivé d'en avoir besoin.
    Ah, si, pour prévoir des stubs (pour bouchonner mon code), une @property qui n'était pas sensée être readwrite dans le cas d'utilisation normale, car les données étaient téléchargées automatiquement par la classe elle-même, et les variables d'instances affectées en interne... Mais pour les stubs pour bouchonner le temps que le serveur soit prêt, j'avais une classe (que j'incluais dans le target ou non selon mode ouchonné ou pas) qui me rajoutais ce setter utile pour "tricher" pour le cas bouchonné...

    Mais bon, c'est encore un cas un peu alambiqué qu'on ne rencontre pas tous les jours ;D
  • ThibautThibaut Membre
    21:35 modifié #27
    Les frameworks sont une bonne application. Cela évite de montrer des méthodes pas forcément utiles pour l'utilisateur du framework, mais tout de même utiles au framework.
  • ClicCoolClicCool Membre
    novembre 2009 modifié #28
    Mon soucis n'est pas d'éviter qu'un petit malin utilise des undocumented methodes à  ses risques et périls, d'autant que quand les undocumented aboutissent à  de superbes fenêtres volantes je ne peux qu'admirer ;)

    dans 1258054555:
    .../...
    Sinon, dans tous les cas, à  partir du moment où tu déclares une méthode qui pourra modifier ta variable, même si elle est que dans le .m, si tu connais son nom tu pourras toujours l'appeler, même si c'est dans une extension privée (utiliser [tt]@interface MaClasse()[/tt] dans le .m.pour définir des méthodes privées cachées du .h) :../...


    Mais dans ce cas, sans être capilotracté, le mieux me semble de carrément ne pas déclarer d'ivar et de mettre la property dans une extension privée.
    Le modern-RunTime se chargera alors de générer l'ivar ni vu ni connu ;)




    Mais comme je le disais c'est pas des bidouilleurs que je me méfie mais plus des maladresses dues à  une inconscience de la portée des property et des accesseurs. (comme dans mon exemple de binding)
    C'est pour ça que si une iVar doit véritablement être protégée, il vaut mieux se passer de déclarer une property liée "juste" pour faciliter le codage des méthodes propres à  la classe.
  • ThibautThibaut Membre
    21:35 modifié #29
    dans 1258060093:

    Mais dans ce cas, sans être capilotracté, le mieux me semble de carrément ne pas déclarer d'ivar et de mettre la property dans une extension privée.
    Le modern-RunTime se chargera alors de générer l'ivar ni vu ni connu ;)

    C'est possible ça ?
  • ClicCoolClicCool Membre
    21:35 modifié #30
    oui.

    With the modern runtime, if you do not provide an instance variable, the compiler adds one for you.


    C'est ici
  • ThibautThibaut Membre
    21:35 modifié #31
    Oui et non. Je viens de tester, cela ne peut pas être fait dans les catégories.
Connectez-vous ou Inscrivez-vous pour répondre.