Private ivar vs property dans category () avec ARC

Bonjour,


 


Petite question du soir, avant ARC l'utilisations des propriétés dans la category anonyme d'une classe étaient bien pratique pour avoir quelquechose de privé avec getter et setter mais avec ARC y a-t-il encore un intérêt ? que conseillez vous ?



//Cas 1
//dans le .h
@interface MyClass : NSObject
{
@private
MyObject * _object1; //par défaut __strong
}

@end

//dans le .m
@implementation MyClass

@end


// //cas 2
//dans le .h
@interface MyClass : NSObject
{

}

@end

//dans le .m
@interface MyClass ()
@property (readwrite, strong) MyObject * _object1;
@end

@implementation MyClass

@end

Réponses

  • JE729JE729 Membre
    janvier 2014 modifié #2

    Dans le meme style, que choisit on entre le cas 3 et 4 et pourquoi ?



    //Cas 1
    //dans le .h
    @interface MyClass : NSObject
    {
    @private
    int _myNumber;
    }

    @end

    //dans le .m
    @implementation MyClass

    @end


    // //cas 2
    //dans le .h
    @interface MyClass : NSObject
    {

    }

    @end

    //dans le .m
    @interface MyClass ()
    @property (readwrite, assign) int myNumber;
    @end

    @implementation MyClass

    @end

    J'espère que c'est pas une question trop bête.


  • AliGatorAliGator Membre, Modérateur

    Bonjour,
     
    Petite question du soir, avant ARC l'utilisations des propriétés dans la category anonyme d'une classe étaient bien pratique pour avoir quelquechose de privé avec getter et setter mais avec ARC y a-t-il encore un intérêt ?

    C'est quoi le rapport entre ARC ou pas d'un côté, et la déclaration dans le .m des propriétés pour les rendre privées de l'autre ?!?

    Et évidemment qu'il y a un intérêt à  le faire, et pas des moindres : le respect du principe de base d'encapsulation en POO.

    Dans le meme style, que choisit on entre le cas 3 et 4 et pourquoi ?

    - Pas besoin de déclarer de variables d'instances puisque les propriétés les synthétisent pour toi depuis plusieurs versions du compilateur. Du coup plus besoin non plus des accolades, si tu ne mets aucune variable d'instance dedans.
    - Le but d'un fichier ".h" est d'être l'API publique d'une classe, donc d'exposer le strict minimum à  l'utilisateur de la classe, pour qu'il sache juste ce dont il a besoin et pas plus (encapsulation on a dit) donc laisser transparaà®tre le moins possible de détails d'implémentation.

    Donc évidemment la bonne solution à  suivre est le dernier cas :
    // Dans le .h
    @interface MyClass : NSObject
    @end

    // Dans le .m
    @interface MyClass()
    @property(assign) int myNumber;
    @end

    @implementation MyClass
    @end
  • Je parle d'ARC car quand il y'avait pas ARC, le fait de mettre des propriétés au lieu des ivars m'évitait d'écrire moi même des setters et des getters pour faire le release et retain des objets. Par exemple : 



    //ARC
    self.myObject = anObject;
    //correspond à 
    _myObject = anObject;

    //Sans ARC
    self.myObject = anObject;
    //correspond à 
    [_myObject release];
    _myObject = [anObject retain];

    et du coup je trouvais ça plus rapide de mettre des ivars en private que d'écrire une catégorie pour faire la meme chose.


     


    Bref Merci AliGator.


    Ta réponse est claire et précise. C'est vrai qu'il est plus logique que les autres classes en sache le moins possible.


  • AliGatorAliGator Membre, Modérateur

    Je parle d'ARC car quand il y'avait pas ARC, le fait de mettre des propriétés au lieu des ivars m'évitait d'écrire moi même des setters et des getters pour faire le release et retain des objets. Par exemple : 


    //ARC
    self.myObject = anObject;
    //correspond à 
    _myObject = anObject;

    //Sans ARC
    self.myObject = anObject;
    //correspond à 
    [_myObject release];
    _myObject = [anObject retain];

    Cela n'a toujours rien à  voir avec ARC, tu confonds. La seule chose que ARC apporte dans le sujet que tu évoques là , c'est "_myObject = anObject" qui équivaut à  "[_myObject release]" + "_myObject = [anObject retain]", car ARC sait relâcher les variables d'instances tout seul, ce qui tu étais obligé de faire avant soit en écrivant manuellement les setters, soit en utilisant une propriété qui justement fait ça pour toi.

    - Le fait de déclarer une @property déclare les getters et les setters.
    - Mais surtout, le fait de les synthétiser automatiquement (@synthesize) permet de demander au compilateur de les implémenter pour toi (plutôt que d'écrire manuellement le setter qui fait le release de l'ancien + le retain du nouvel objet)
    - Et tout ces trucs sur les propriétés, dont les setters synthétisés font tout ce qui est nécessaire côté gestion mémoire, n'a rien à  voir avec ARC ou pas ARC
    - Et de plus maintenant avec les derniers compilateurs (depuis plusieurs versions de LLVM déjà ), il n'est plus nécessaire de demander explicitement la synthèse des propriétés. Autrement dit on peut se passer d'écrire la directive @synthesize, il le fait tout seul.

    Ce que fait un @synthesize, qu'il soit explicite ou implicite, c'est d'implémenter pour toi le setter et le getter associé à  une @property, pour pas que tu n'aies à  l'écrire manuellement. Dans ces implémentations synthétisées, il fait tout ce qu'il faut d'après les attributs associés à  la propriété (si attribut "nonatomic" absent, gestion de l'atomicité de la variable avec un Mutex/SpinLock/... ; si attribut "copy", copie de l'objet ; si attribut "retain"/"strong", gestion mémoire appropriée, etc, etc).

    ---

    Donc quand tu écris le code que j'ai écrit au dessus dans ma réponse #3, self.myNumber fait ce qu'il faut en terme de gestion mémoire... qu'ARC soit activé ou non, ça ne change rien.

    Par contre, la seule chose qu'ARC change dans tout ça, c'est si tu t'avères de manipuler direment les ivars, où là  sans ARC du coup il fallait faire attention à  bien release puis retain " et que pour éviter de faire des bétises il était du coup préférable de directement appeler le setter qui se chargeait de faire ça pour toi justement " alors qu'avec ARC même si tu manipules directement l'ivar et pas la propriété il va correctement libérer les objets inutilisés et retenir les objets strong-owned.

    Mais dans tous les cas, ça fait une éternité (bien avant ARC d'ailleurs) que je conseille dans ces forums, tout comme Apple dans ses discours, d'abandonner la déclaration et manipulation des ivars directement (d'autant plus maintenant qu'on a l'Automatic Property Synthesis depuis plusieurs verions de LLVM) et de toujours manipuler les @property (j'en viens à  oublier que les ivars ont existé et à  ne plus penser qu'en terme de propriétés). Car utiliser les @property est plus sûr, et bien plus conforme au principe d'encapsulation de la POO encore une fois, et te permet de rester cohérent et consistant dans tes données (centraliser la façon de modifier une propriété de ton objet, pour pouvoir mettre un breakpoint si besoin à  cet endroit, mettre du code en commun le cas échéant, déclencher le KVO si nécessaire, etc...).

    Oublie les ivars, sors-les de ta tête, à  part pour des optimisations bien pointues ou pour implémenter ton propre setter/getter, tu n'auras plus jamais besoin d'en avoir, c'est tellement plus simple et moins prise de tête de tout faire avec des propriétés et laisser le compilateur générer tout ce qu'il faut (backing ivars, setter, getter, KVO, MemoryMgmt, Thread-Safety, et tout ça) pour toi de façon transparente. D'ailleurs si tu avais déjà  suivi ce précepte depuis qques temps (depuis qu'Apple en parle et que je le préconise ici ^^ donc bien avant ARC) tu ne te poserais pas cette question aujourd'hui car tu ne manipulerais plus du tout d'ivar et ARC n'aurait rien changé de ce côté-là . Le @synthesize ainsi que l'Automatic-Property-Synthesisis existent depuis qques temps, indépendemment d'ARC.
  • Tout d'abord merci pour cette longue réponse détaillée.


     



    Par contre, la seule chose qu'ARC change dans tout ça, c'est si tu t'avères de manipuler direment les ivars, où là  sans ARC du coup il fallait faire attention à  bien release puis retain " et que pour éviter de faire des bétises il était du coup préférable de directement appeler le setter qui se chargeait de faire ça pour toi justement " alors qu'avec ARC même si tu manipules directement l'ivar et pas la propriété il va correctement libérer les objets inutilisés et retenir les objets strong-owned.



    Quand je parlais d'ARC je voulais parler de ça mais tu l'as dit beaucoup mieux que moi et sans confusion.

     



    D'ailleurs si tu avais déjà  suivi ce précepte depuis qques temps (depuis qu'Apple en parle et que je le préconise ici ^^ donc bien avant ARC) tu ne te poserais pas cette question aujourd'hui car tu ne manipulerais plus du tout d'ivar et ARC n'aurait rien changé de ce côté-là .



    À vrai dire quand j'ai commencé l'objective-c, j'ai directement commencé avec les property (dans la category anonyme si privée) et les synthesize et je n'ai vu des ivars que très rarement (souvent les .h d'Apple). C'est pourquoi je posais la question ce soir car je commence mon premier projet avec ARC. J'ai lu la doc, deux trois articles sur ARC et je me suis retrouvé naif face à  ces ivars.



     


  • JE729JE729 Membre
    janvier 2014 modifié #7

    J'ai lu une fois que tu disais de mettre les IBOutlet en privée. Or lorsqu' ARC est activée, ca ne marche pas avec NSTextView.


    Du coup, il faut apparemment faire ca :



    @interface MyClass ()
    {
    IBOutlet NSTextView *textView;// retour des ivars pour que tout se passe bien
    }

    @property IBOutlet NSTextView *textView; //pour avoir les avantages relatifs au property
    @property (weak) IBOutlet NSButton *button; //autres objets
    @end

  • AliGatorAliGator Membre, Modérateur
    Je vois encore moins le rapport entre ARC et les IBOutlets, c'est quoi cette obstination ?

    Sinon :

    1) Pour rappel, IBOutlet est un mot clé (#defini à  ... rien du tout) qui ne sert à  rien d'autre qu'à  permettre à  IB quand il parse le fichier de détecter les propriétés ou ivars à  faire transparaà®tre dans IB.

    2) Pas besoin de mettre 2x IBOutlet. Soit tu crées une ivar et choisis de mettre l'IBOutlet dessus, soit tu mets ton IBOutlet sur la propriété et du coup si tu choisi de déclarer la backing ivar assicée explicitement pas la peine de lui remettre le mot clé au risque que IB le détecte 2x quand il va parser le fichier... voire que l'IBOutlet de la propriété textView / l'ivar généréé _textView ne soit pas exposée car shadowée par l'ivar textView...

    3) En plus dans ton exemple, à  moins que tu mettes un "@synthesize textView;" ou "@synthesize textView = textView" explicite dans le .m, la @property ne sera PAS associée à  cette ivar mais à  l'ivar "_textView" (qu'il va synthétiser pour l'occasion à  la compilation) car le @synthesize implicite revient à  écrire "@synthesize textView = _textView;". Du coup ça va être encore + le bordel car tu vas avoir 2 ivars, une "textView" déclarée explicitement et une _textView déclarée implicitement et que tu vas manipuler au travers de la propriété textView... tous les ingrédients pour être sûr de s'embrouiller.


    Et tous ces details n'ont, encore une fois, rien à  voir avec ARC (encore moins que pour la question d'avant d'ailleurs).

    (Et puis pourquoi ça marcherait avec NSButton mais pas NSTextView comme le laisse penser ton exemple, en plus ?!)
  • JE729JE729 Membre
    janvier 2014 modifié #9

    Lol je ne m'obstine pas je cherche juste à  comprendre


     


    Car je me suis retrouvé avec l'image ci-joint et au lieu de copier bêtement la solution trouvable sur internet. J'ai cherché à  comprendre, puis de file en aiguille je suis tombé sur les ivars que j'ai comparé avec les propriétés privées et puis je me suis un peu perdu dans tout ca et je cherchais à  connaitre les bonnes manières de faire les choses.


     


    Le sujet du post est éloigné de mon problème initiale mais n'étant pas pro et ne connaissant pas vraiment les ivars, je voulais savoir si la manière de faire du privée était toujours la meme malgré ARC et j'ai eu ma réponse --> ca change rien de ce point de vu la. On n'utilise plus les ivars (depuis longtemps) car on a les property et tu m'as bien expliqué pourquoi.

  • AliGatorAliGator Membre, Modérateur
    Bah l'erreur est pourtant explicite : NSTextView ne supporte pas les weak références (il y a encore de très rares classes où ce n'est pas le cas en effet car Apple n'a pas dû finir de tout migrer dans AppKit :-( il serait temps pourtant avec Mavericks)


    La solution est donc simple : tu ne peux pas utiliser weak dessus, il faut donc utiliser strong.


    Cette erreur n'a rien à  voir avec IBOutlet. Mais alors rien du tout. Il faudrait peut-être lire les erreurs (pourtant explicites) avant de tirer des conclusions hâtives qui n'ont rien a voir sinon tu vas avoir du mal à  les résoudre correctement si tu suis même pas les instructions que te donne le compilateur ^^


    Le fait de rajouter ivar fait disparaà®tre l'erreur car tu n'as pas mis de qualificatif __weak à  ton ivar (ce qui, au passage, la rend incohérente avec les qualificatifs de la propriété associée) donc par défaut elle est __strong et du coup il n'y a plus le problème du weak. C'est un coup de bol / moyen détourné qui rend ton code incohérent et masque l'erreur au lieu de la corriger. Et le tout à  encore une fois rien à  voir avec IBOutlet (que tu mettes le mot clé ou pas n'y changera rien à  l'erreur initiale qui est sur le "weak" et pas sur le mot clé IBOutlet qui de toute façon est #define à  "" (rien) donc ne change que dalle pour le compilateur et ne sert qu'au parseur IB)
  • Question pour Ali: si je mets:



    MonObjet.h :

    @interface MonObjet : NSObject
    @property IBOutlet NSButton *monBouton
    @property IBOutlet NSTextView *monTexte
    @end

    MonObjet.m :

    #import "MonObjet.h"
    @implementation
    @end

    Tout sec, il se passe quoi? En principe rien pour le NSTextView, puisque le défaut est "strong", mais je risque le retain cycle sur le bouton, c'est ça?


  • AliGatorAliGator Membre, Modérateur
    Non, rien à  voir avec le retain cycle (faut pas tout confondre toi non plus :D)

    Par défaut pour les objets c'est strong il me semble. Du coup tu risques la même chose pour ton NSTextView et ton NSButton (je vois pas pourquoi il y aurait une distinction ?!), les deux sont "strong", donc tant que ton instance de MonObject est en vie, il va retenir ces 2 instances, et il ne les relâchera que lorsque lui-même sera relâché / détruit.

    Un Retain Cycle c'est si ton NSTextView et/ou ton NSButton retenaient à  leur tour ton instance de MonObjet (ce qui n'est évidemment pas le cas ici), ils se retiendraient mutuellement. En bref, le Retain Cycle, c'est l'histoire du "Bon c'est toi qui raccroche ! Non c'est toi !" : chacun ne relâchera l'autre que quand il sera lui-même relâché, mais comme ils se retiennent mutuellement ça n'arrivera jamais.
  • samirsamir Membre
    janvier 2014 modifié #13


     mais je risque le retain cycle sur le bouton, c'est ça?




     


    Non y a pas de retain cycle dans ton cas, puisque ton button n'a aucune référence vers "MonObjet". Un retain cycle c'est quand t'as deux objets qui se référencent d'une manière forte "strong". L'exemple classique est avec le pattern delegation. Y a surement des exemples de code dans le forum qui explique ça en détail.


     


    Sinon les Outlets sont généralement déclarer en weak, sauf pour les vues qui sont en niveaux principal comme par exemple la property "view" de UIViewController qui est déclarer en strong ( puisque c'est la vue principale).


     


    Doc:



    Outlets should generally be weak, except for those from File's Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create should therefore typically be weak, because:



    • Outlets that you create to subviews of a view controller's view or a window controller's window, for example, are arbitrary references between objects that do not imply ownership.



    • The strong outlets are frequently specified by framework classes (for example, UIViewController's view outlet, or NSWindowController's windowoutlet).


    @property (weak) IBOutlet MyView *viewContainerSubview;

    @property (strong) IBOutlet MyOtherClass *topLevelObject;

     



    lien : https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6


  • berfisberfis Membre
    janvier 2014 modifié #14

    Ah oui j'ai confondu >:)  . Merci du rappel. Est-ce qu'on risque le zombie avec un strong?


     


    Question subsidiaire: si on ne fait pas de références mutuelles, qu'est-ce qu'on risque à  ne rien préciser, comme dans mon exemple?


  • samirsamir Membre
    janvier 2014 modifié #15


     Est-ce qu'on risque le zombie avec un strong?




     


    Si c'est une sous-vue, OUI. Le mieux c'est de suivre les recommandations d'Apple.


     




    Question subsidiaire: si on ne fait pas de références mutuelles, qu'est-ce qu'on risque à  ne rien préciser, comme dans mon exemple?




     


    Ne rien préciser = strong ( par defaut c'est strong), donc tu risque que ta sous-vue reste en mémoire alors que sa vue mère ( principale) n'est plus chargée. 


     


    Edit :


     


    Je parlais bien sur des outlets sous iOS. Je ne sais pas si c'est la même chose sous OS X.


  • AliGatorAliGator Membre, Modérateur
    Le coup du "non c'est toi qui raccroche" ça marche aussi avec les Conf Calls, hein :D

    Un Retain Cycle comme son nom l'indique c'est un cycle de "retain". Donc ça marche avec 3, 4, 12 objets. Si tu mets des "strong" partout (ou que tu ne précises jamais rien) tu vas forcément finir par faire une référence mutuelle quelque part, même sans t'en rendre compte, car A va retenir B qui va retenir C qui va retenir D qui va retenir E qui va retenir A et bim.

    De toute façon c'est bizarre comme question. Il y a un sens pour chacun des 2 qualificateurs, chacun a son rôle qui correspond au sens et à  l'utilisation que tu fais de la propriété. C'est comme si tu demandais s'il y avait un risque à  toujours tourner à  gauche en voiture plutôt que parfois tourner à  gauche et parfois à  droite.

    Si un objet A a une propriété vers un objet B, elle doit être strong si sémantiquement parlant A "possède" B (un document "possède" des feuilles, une voiture "possède" des roues), c'est à  dire que B est un élément qui fait partie intégrante de l'objet A (on appelle ça une "Composition" en POO et en UML), il y a une notion d'appartenance. Ou alors de hiérarchie. Alors que si c'est juste une référence, un lien vers un autre objet, la propriété doit être weak.

    Une voiture contient un moteur, donc dans Voiture on va avoir une "@property(strong) Moteur* moteur", mais un moteur le contient pas une voiture, un moteur appartient à  une voiture, on demande "à  quelle voiture appartient ce moteur", on veut juste remonter la relation inverse, donc là  c'est pas de l'appartenance mais juste un lien ascendant, on va avoir une "@property(weak) Voiture* hostingCar" pour pointer sur la voiture qui abrite le moteur. Ca n'aurait aucun sens sémantiquement parlant de mettre "strong" partout sans réfléchir.

    La programmation c'est pas magique. Quand tu fais l'architecture logicielle et le design de ton modèle, tu mets pas strong ou weak au hasard, ou tu ne mets pas juste rien "parce que t'as la flemme de réfléchir à  savoir si faut mettre strong ou weak" et que tu cherches la solution de facilité donc "autant rien mettre". Non. Tu mets le qualificateur qui correspond au contexte, qui a du sens pour cette propriété de ton objet modèle. Oui, je sais, il faut réfléchir un peu quand tu fais ton modèle, et c'est les fêtes on a la flemme, mais bon, si y'avait plus rien à  faire pour être développeur ça serait pas aussi intéressant comme métier :D
  • Bon juste histoire de me faire pardonner ma flemme alors: est-ce que l'outil Leaks peut me montrer si j'ai des objets prétendument décédés (auxquels par chance je ne fais plus appel...) qui sont encore en train de danser la gigue dans la mémoire, enlacés dans une étreinte malsaine?


  • AliGatorAliGator Membre, Modérateur
    Normalement oui c'est aussi fait pour ça... Après j'ai pas Instruments sous la main là  et ne le maà®trise pas assez pour te sortir comme ça comment faire par coeur, mais bon c'est un de ses rôles avec l'outil Zombies.


  • Bon juste histoire de me faire pardonner ma flemme alors: est-ce que l'outil Leaks peut me montrer si j'ai des objets prétendument décédés (auxquels par chance je ne fais plus appel...) qui sont encore en train de danser la gigue dans la mémoire, enlacés dans une étreinte malsaine?




     


    Tu sembles mélanger deux choses différentes: les leaks et les zombies.


     


    Un leak est une fuite mémoire provoquée par un objet qui n'est plus référencé dans le code de l'application et dont la mémoire n'a pas été libérée. Cet objet n'est plus accéssible dans le code, la mémoire occupée par cet objet est donc perdue.


     


    Un zombie est un objet toujours référencé dans le code mais dont la mémoire a été libérée. Cet objet est donc mort et tout accès dans le code à  cet objet fera planter l'application.

  • Non, ça je ne mélange pas... D'ailleurs il y a deux outils différents dans Instruments. Mais il m'est arrivé à  deux reprises en quatre ans de croiser un zombie, jamais une leak. D'ailleurs, croiser un zombie ne passe pas inaperçu... Dans le temps, ça arrivait quand j'avais deux pointeurs sur le même objet, je libérais l'objet et je mettais le pointeur à  nil, et j'oubliais l'autre... jusqu'au moment où le dialogue "Désolé, une erreur système est survenue..." jaillissait sur l'écran. Une leak est bénigne en comparaison...


     


    Mais il est vrai que ARC m'a incité à  une coupable nonchalance. De telles mises au point me sont vraiment utiles, merci de vos explications!


Connectez-vous ou Inscrivez-vous pour répondre.